1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
# 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.source_remove(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
|