# Copyright 1999-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import logging from portage import os from portage.util import writemsg_level from portage.util.futures import asyncio 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() and self.pid is not None: 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 _async_wait(self): if self.returncode is None: raise asyncio.InvalidStateError('Result is not ready for %s' % (self,)) else: # This calls _unregister, so don't call it until pid status # is available. super(SubProcess, self)._async_wait() 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