| # Copyright 1999-2012 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| from portage import os |
| from _emerge.AbstractPollTask import AbstractPollTask |
| import signal |
| import errno |
| |
| class SubProcess(AbstractPollTask): |
| |
| __slots__ = ("pid",) + \ |
| ("_files", "_reg_id") |
| |
| # A file descriptor is required for the scheduler to monitor changes from |
| # inside a poll() loop. When logging is not enabled, create a pipe just to |
| # serve this purpose alone. |
| _dummy_pipe_fd = 9 |
| |
| # This is how much time we allow for waitpid to succeed after |
| # we've sent a kill signal to our subprocess. |
| _cancel_timeout = 1000 # 1 second |
| |
| def _poll(self): |
| if self.returncode is not None: |
| return self.returncode |
| if self.pid is None: |
| return self.returncode |
| if self._registered: |
| return self.returncode |
| |
| try: |
| # With waitpid and WNOHANG, only check the |
| # first element of the tuple since the second |
| # element may vary (bug #337465). |
| retval = os.waitpid(self.pid, os.WNOHANG) |
| except OSError as e: |
| if e.errno != errno.ECHILD: |
| raise |
| del e |
| retval = (self.pid, 1) |
| |
| if retval[0] == 0: |
| return None |
| self._set_returncode(retval) |
| self.wait() |
| return self.returncode |
| |
| def _cancel(self): |
| if self.isAlive(): |
| try: |
| os.kill(self.pid, signal.SIGTERM) |
| except OSError as e: |
| if e.errno != errno.ESRCH: |
| raise |
| |
| def isAlive(self): |
| return self.pid is not None and \ |
| self.returncode is None |
| |
| def _wait(self): |
| |
| if self.returncode is not None: |
| return self.returncode |
| |
| if self._registered: |
| if self.cancelled: |
| self._wait_loop(timeout=self._cancel_timeout) |
| if self._registered: |
| try: |
| os.kill(self.pid, signal.SIGKILL) |
| except OSError as e: |
| if e.errno != errno.ESRCH: |
| raise |
| del e |
| self._wait_loop(timeout=self._cancel_timeout) |
| if self._registered: |
| self._orphan_process_warn() |
| else: |
| self._wait_loop() |
| |
| if self.returncode is not None: |
| return self.returncode |
| |
| if not isinstance(self.pid, int): |
| # Get debug info for bug #403697. |
| raise AssertionError( |
| "%s: pid is non-integer: %s" % |
| (self.__class__.__name__, repr(self.pid))) |
| |
| self._waitpid_loop() |
| |
| return self.returncode |
| |
| def _waitpid_loop(self): |
| source_id = self.scheduler.child_watch_add( |
| self.pid, self._waitpid_cb) |
| try: |
| while self.returncode is None: |
| self.scheduler.iteration() |
| finally: |
| self.scheduler.source_remove(source_id) |
| |
| def _waitpid_cb(self, pid, condition, user_data=None): |
| if pid != self.pid: |
| raise AssertionError("expected pid %s, got %s" % (self.pid, pid)) |
| self._set_returncode((pid, condition)) |
| |
| def _orphan_process_warn(self): |
| pass |
| |
| def _unregister(self): |
| """ |
| Unregister from the scheduler and close open files. |
| """ |
| |
| self._registered = False |
| |
| if self._reg_id is not None: |
| self.scheduler.unregister(self._reg_id) |
| self._reg_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 |
| |
| def _set_returncode(self, wait_retval): |
| """ |
| Set the returncode in a manner compatible with |
| subprocess.Popen.returncode: A negative value -N indicates |
| that the child was terminated by signal N (Unix only). |
| """ |
| self._unregister() |
| |
| pid, status = wait_retval |
| |
| if os.WIFSIGNALED(status): |
| retval = - os.WTERMSIG(status) |
| else: |
| retval = os.WEXITSTATUS(status) |
| |
| self.returncode = retval |
| |