Use PollScheduler iteration method.
[portage.git] / pym / _emerge / SubProcess.py
1 # Copyright 1999-2012 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from portage import os
5 from _emerge.AbstractPollTask import AbstractPollTask
6 import signal
7 import errno
8
9 class SubProcess(AbstractPollTask):
10
11         __slots__ = ("pid",) + \
12                 ("_files", "_reg_id")
13
14         # A file descriptor is required for the scheduler to monitor changes from
15         # inside a poll() loop. When logging is not enabled, create a pipe just to
16         # serve this purpose alone.
17         _dummy_pipe_fd = 9
18
19         # This is how much time we allow for waitpid to succeed after
20         # we've sent a kill signal to our subprocess.
21         _cancel_timeout = 1000 # 1 second
22
23         def _poll(self):
24                 if self.returncode is not None:
25                         return self.returncode
26                 if self.pid is None:
27                         return self.returncode
28                 if self._registered:
29                         return self.returncode
30
31                 try:
32                         # With waitpid and WNOHANG, only check the
33                         # first element of the tuple since the second
34                         # element may vary (bug #337465).
35                         retval = os.waitpid(self.pid, os.WNOHANG)
36                 except OSError as e:
37                         if e.errno != errno.ECHILD:
38                                 raise
39                         del e
40                         retval = (self.pid, 1)
41
42                 if retval[0] == 0:
43                         return None
44                 self._set_returncode(retval)
45                 self.wait()
46                 return self.returncode
47
48         def _cancel(self):
49                 if self.isAlive():
50                         try:
51                                 os.kill(self.pid, signal.SIGTERM)
52                         except OSError as e:
53                                 if e.errno != errno.ESRCH:
54                                         raise
55
56         def isAlive(self):
57                 return self.pid is not None and \
58                         self.returncode is None
59
60         def _wait(self):
61
62                 if self.returncode is not None:
63                         return self.returncode
64
65                 if self._registered:
66                         if self.cancelled:
67                                 timeout = self._cancel_timeout
68                                 self.scheduler.schedule(self._reg_id, timeout=timeout)
69                                 if self._registered:
70                                         try:
71                                                 os.kill(self.pid, signal.SIGKILL)
72                                         except OSError as e:
73                                                 if e.errno != errno.ESRCH:
74                                                         raise
75                                                 del e
76                                         self.scheduler.schedule(self._reg_id, timeout=timeout)
77                                         if self._registered:
78                                                 self._orphan_process_warn()
79                         else:
80                                 while self._registered:
81                                         self.scheduler.iteration()
82                         self._unregister()
83                         if self.returncode is not None:
84                                 return self.returncode
85
86                 try:
87                         # With waitpid and WNOHANG, only check the
88                         # first element of the tuple since the second
89                         # element may vary (bug #337465).
90                         wait_retval = os.waitpid(self.pid, os.WNOHANG)
91                 except OSError as e:
92                         if e.errno != errno.ECHILD:
93                                 raise
94                         del e
95                         self._set_returncode((self.pid, 1 << 8))
96                 else:
97                         if wait_retval[0] != 0:
98                                 self._set_returncode(wait_retval)
99                         else:
100                                 try:
101                                         wait_retval = os.waitpid(self.pid, 0)
102                                 except OSError as e:
103                                         if e.errno != errno.ECHILD:
104                                                 raise
105                                         del e
106                                         self._set_returncode((self.pid, 1 << 8))
107                                 else:
108                                         self._set_returncode(wait_retval)
109
110                 return self.returncode
111
112         def _orphan_process_warn(self):
113                 pass
114
115         def _unregister(self):
116                 """
117                 Unregister from the scheduler and close open files.
118                 """
119
120                 self._registered = False
121
122                 if self._reg_id is not None:
123                         self.scheduler.unregister(self._reg_id)
124                         self._reg_id = None
125
126                 if self._files is not None:
127                         for f in self._files.values():
128                                 if isinstance(f, int):
129                                         os.close(f)
130                                 else:
131                                         f.close()
132                         self._files = None
133
134         def _set_returncode(self, wait_retval):
135                 """
136                 Set the returncode in a manner compatible with
137                 subprocess.Popen.returncode: A negative value -N indicates
138                 that the child was terminated by signal N (Unix only).
139                 """
140
141                 pid, status = wait_retval
142
143                 if os.WIFSIGNALED(status):
144                         retval = - os.WTERMSIG(status)
145                 else:
146                         retval = os.WEXITSTATUS(status)
147
148                 self.returncode = retval
149