# Copyright 1999-2018 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import logging from portage import os from portage.util import writemsg_level from _emerge.AbstractPollTask import AbstractPollTask import signal import errno class SubProcess(AbstractPollTask): __slots__ = ("pid",) + \ ("_dummy_pipe_fd", "_files", "_waitpid_id") # This is how much time we allow for waitpid to succeed after # we've sent a kill signal to our subprocess. _cancel_timeout = 1 # seconds def _poll(self): # Simply rely on _async_waitpid_cb to set the returncode. return self.returncode def _cancel(self): if self.isAlive(): try: os.kill(self.pid, signal.SIGTERM) except OSError as e: if e.errno == errno.EPERM: # Reported with hardened kernel (bug #358211). writemsg_level( "!!! kill: (%i) - Operation not permitted\n" % (self.pid,), level=logging.ERROR, noiselevel=-1) elif e.errno != errno.ESRCH: raise def isAlive(self): return self.pid is not None and \ self.returncode is None def _async_waitpid(self): """ Wait for exit status of self.pid asynchronously, and then set the returncode and notify exit listeners. This is prefered over _waitpid_loop, since the synchronous nature of _waitpid_loop can cause event loop recursion. """ if self.returncode is not None: self._async_wait() elif self._waitpid_id is None: self._waitpid_id = self.pid self.scheduler._asyncio_child_watcher.\ add_child_handler(self.pid, self._async_waitpid_cb) def _async_waitpid_cb(self, pid, returncode): if pid != self.pid: raise AssertionError("expected pid %s, got %s" % (self.pid, pid)) self.returncode = returncode self._async_wait() def _orphan_process_warn(self): pass def _unregister(self): """ Unregister from the scheduler and close open files. """ self._registered = False if self._waitpid_id is not None: self.scheduler._asyncio_child_watcher.\ remove_child_handler(self._waitpid_id) self._waitpid_id = None if self._files is not None: for f in self._files.values(): if isinstance(f, int): os.close(f) else: f.close() self._files = None