def exec_under_watch(process_args: List[str], **kwargs: Any) -> Optional[Popen]: msg = ( "The Janitor can not work with `shell=True`. When that flag " "is used a proxy shell process is used to start the real " "target process, the result is that the Janitor will monitor " "and kill the proxy shell instead of the target, once the " "shell is killed the real targets are kept around as " "orphans, which is exactly what the Janitor is trying to " "prevent from happening.") assert not kwargs.get("shell", False), msg def subprocess_stopped(result: AsyncResult) -> None: if janitor._exit_in_progress: # During __exit__ we expect processes to stop, since # they are killed by the janitor. return with janitor._processes_lock: # Processes are expected to quit while the nursery is # active, remove them from the track list to clear memory janitor._processes.remove(process) # if the subprocess error'ed propagate the error. try: exit_code = result.get() if exit_code != STATUS_CODE_FOR_SUCCESS: log.error( "Process died! Bailing out.", args=process.args, exit_code=exit_code, ) exception = SystemExit(exit_code) janitor._stop.set_exception(exception) except Exception as exception: janitor._stop.set_exception(exception) with janitor._processes_lock: if janitor._stop.ready(): return None process = Popen(process_args, **kwargs) janitor._processes.add(process) # `rawlink`s are executed from within the hub, the problem # is that locks can not be acquire at that point. # SpawnedLink creates a new greenlet to run the callback to # circumvent that. callback = SpawnedLink(subprocess_stopped) process.result.rawlink(callback) # Important: `stop` may be set after Popen started, but before # it returned. If that happens `GreenletExit` exception is # raised here. In order to have proper cleared, exceptions have # to be handled and the process installed. if janitor._stop.ready(): process.send_signal(signal.SIGINT) return process
def watch(self, callback): current = greenlet.getcurrent() tid = self.get() if hasattr(current, 'link'): # This is a Gevent Greenlet (capital G), which inherits from # greenlet and provides a 'link' method to detect when the # Greenlet exits. link = SpawnedLink(callback) current.rawlink(link) self._refs[tid] = link else: # This is a non-Gevent greenlet (small g), or it's the main # greenlet. self._refs[tid] = weakref.ref(current, callback)
def spawn_under_watch(function: Callable, *args: Any, **kwargs: Any) -> Greenlet: greenlet = gevent.spawn(function, *args, **kwargs) def kill_grenlet(_result: AsyncResult) -> None: # gevent.GreenletExit must **not** be used here, since that # exception is considered a successful run and it is not # re-raised on calls to `get` exception = RuntimeError("Janitor is stopping") greenlet.throw(exception) # The Event.rawlink is executed inside the Hub thread, which # does validation and *raises on blocking calls*, to go around # this a new greenlet has to be spawned, that in turn will # raise the exception. callback = SpawnedLink(kill_grenlet) janitor._stop.rawlink(callback) return greenlet