Skip to content

Conversation

@douglas-raillard-arm
Copy link
Collaborator

@douglas-raillard-arm douglas-raillard-arm commented Apr 6, 2023

Ensure all background commands are terminated before we close the connection.

This PR adds tracking of all outstanding BackgroundCommand inside ConnectionBase._current_bg_cmds. This attribute is changed from WeakSet() to set(), and the BackgroundCommand themselves deregister themselves when they terminate. This allows properly canceling all outstanding_ bg command when the connection is closed.

BackgroundCommand.init polls current bg cmds of its connection to reap any terminated commands, so that APIs that leak objects like the change introduced by this PR:
#624
or any other non-careful user does not result in endless leak of associated resources.

@douglas-raillard-arm douglas-raillard-arm mentioned this pull request Apr 6, 2023
@douglas-raillard-arm douglas-raillard-arm changed the title WIP connection: Terminate background commands on close() connection: Terminate background commands on close() Apr 6, 2023
# accumulating terminated commands and therefore leaking associated
# resources if the user is not careful and does not use the context
# manager API.
for bg_cmd in conn._current_bg_cmds:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to spend some more time narrowing down the problem, however when I combine these changes with #624 on an AndroidTarget I'm run into the following error:

self.target.pull(self.target_output_path, host_output_file)
File ".../devlib/utils/asyn.py", line 209, in __call__
  return self.blocking(*args, **kwargs)
File ".../devlib/utils/asyn.py", line 338, in blocking
  return asyncio.run(wrapper())
File ".../lib/python3.8/site-packages/nest_asyncio.py", line 32, in run
  return loop.run_until_complete(future)
File ".../lib/python3.8/site-packages/nest_asyncio.py", line 70, in run_until_complete
  return f.result()
File "/usr/lib/python3.8/asyncio/futures.py", line 178, in result
  raise self._exception
File "/usr/lib/python3.8/asyncio/tasks.py", line 280, in __step
  result = coro.send(None)
File ".../devlib/utils/asyn.py", line 337, in wrapper
  return await x
File ".../devlib/target.py", line 797, in pull
  do_pull(sources, dest)
File ".../devlib/target.py", line 786, in do_pull
  self.conn.pull(sources, dest, timeout=timeout)
File ".../devlib/utils/misc.py", line 941, in wrapper
  return f_(*args, **kwargs)
File ".../devlib/utils/android.py", line 322, in pull
  return self._push_pull('pull', sources, dest, timeout)
File ".../devlib/utils/misc.py", line 941, in wrapper
  return f_(*args, **kwargs)
File ".../devlib/utils/android.py", line 337, in _push_pull
  bg_cmd = adb_command_background(
File ".../devlib/utils/android.py", line 703, in adb_command_background
  cmd = PopenBackgroundCommand(conn=conn, popen=popen)
File ".../devlib/connection.py", line 295, in __init__
  super().__init__(conn=conn)
File ".../devlib/connection.py", line 132, in __init__
  for bg_cmd in conn._current_bg_cmds:

Sending error-logged from <ErrorSignalHandler (DEBUG)>
RuntimeError(Set changed size during iteration)



Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, poll() will remove the command from the set if the command has completed, and that is the exact reason we are calling poll() here. I just need to "freeze" the set we are iterating over.

Ensure all background commands are terminated before we close the
connection.
Add a constructor to BackgroundCommand so that the command knows the
connection it's tied to.
Instead of loosely tracking the current BackgroundCommand for a
connection in _current_bg_cmds WeakSet attribute, use a normal set and
make the BackgroundCommand deregister itself upon termination.

This allows canceling any outstanding BackgroundCommand when the
connection is closed. Currently, destroying a BackgroundCommand will not
cancel the command but devlib will simply loose track of it, and some
threads will likely fail in the background if they try to use the now
broken connection.
Make BackgroundCommand.__init__() poll all current BackgroundCommands on
the associated connection so they deregister themselves if they are
completed.

This ensures that a BackgroundCommand-heavy application that also does
not close them properly will not accumulate useless instances forever
and leak associated resources like Popen objects.
@douglas-raillard-arm
Copy link
Collaborator Author

Updated PR with fix for RuntimeError(Set changed size during iteration)

@marcbonnici marcbonnici merged commit 45aebda into ARM-software:master Apr 29, 2023
@douglas-raillard-arm douglas-raillard-arm mentioned this pull request May 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants