Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 351 lines (298 sloc) 12.043 kB
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
1 #!/usr/bin/env python
2 #
3 # Copyright 2011 Facebook
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
15 # under the License.
16
693ebbe @bdarnell Doc updates, modules N-T.
bdarnell authored
17 """Utilities for working with multiple processes, including both forking
18 the server into multiple processes and managing subprocesses.
19 """
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
20
65df55d @bdarnell Convert print to a function and add future imports.
bdarnell authored
21 from __future__ import absolute_import, division, print_function, with_statement
da6d821 @bdarnell Standardize future imports for all files in the package.
bdarnell authored
22
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
23 import errno
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
24 import os
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
25 import signal
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
26 import subprocess
70bcb6e @bdarnell Refactor random-seed code out of fork_processes
bdarnell authored
27 import sys
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
28 import time
29
70bcb6e @bdarnell Refactor random-seed code out of fork_processes
bdarnell authored
30 from binascii import hexlify
31
2467782 @bdarnell Add Subprocess.wait_for_exit, a coro-friendly set_exit_callback().
bdarnell authored
32 from tornado.concurrent import Future
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
33 from tornado import ioloop
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
34 from tornado.iostream import PipeIOStream
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
35 from tornado.log import gen_log
3cf9098 @bdarnell Set CLOEXEC on subprocess pipe endpoints so they are not inherited by…
bdarnell authored
36 from tornado.platform.auto import set_close_exec
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
37 from tornado import stack_context
f81a25e @cardoe define and use errno_from_exception abstraction
cardoe authored
38 from tornado.util import errno_from_exception
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
39
40 try:
4b6fc9c @bdarnell Fix Tornado on Google App Engine.
bdarnell authored
41 import multiprocessing
42 except ImportError:
61e87cd @leekchan Fixed some typos
leekchan authored
43 # Multiprocessing is not available on Google App Engine.
4b6fc9c @bdarnell Fix Tornado on Google App Engine.
bdarnell authored
44 multiprocessing = None
45
46 try:
900d5a2 @bdarnell Get all the tests passing under py3 without 2to3
bdarnell authored
47 long # py2
48 except NameError:
49 long = int # py3
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
50
e582e58 @bdarnell Update autopep8 to 0.8.5 and run it.
bdarnell authored
51
2bd2f0d @bdarnell Add raise_error behavior to Subprocess.wait_for_exit.
bdarnell authored
52 # Re-export this exception for convenience.
53 CalledProcessError = subprocess.CalledProcessError
54
55
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
56 def cpu_count():
57 """Returns the number of processors on this machine."""
4b6fc9c @bdarnell Fix Tornado on Google App Engine.
bdarnell authored
58 if multiprocessing is None:
59 return 1
dec2d74 @bdarnell No more need to check for 'ssl is None' or 'multiprocessing is None'
bdarnell authored
60 try:
61 return multiprocessing.cpu_count()
62 except NotImplementedError:
63 pass
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
64 try:
65 return os.sysconf("SC_NPROCESSORS_CONF")
66 except ValueError:
67 pass
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
68 gen_log.error("Could not detect number of processors; assuming 1")
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
69 return 1
70
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
71
70bcb6e @bdarnell Refactor random-seed code out of fork_processes
bdarnell authored
72 def _reseed_random():
73 if 'random' not in sys.modules:
74 return
75 import random
76 # If os.urandom is available, this method does the same thing as
77 # random.seed (at least as of python 2.6). If os.urandom is not
78 # available, we mix in the pid in addition to a timestamp.
79 try:
80 seed = long(hexlify(os.urandom(16)), 16)
81 except NotImplementedError:
7d32d64 Fix _reseed_random when os.urandom is not implemented
Thomas Miedema authored
82 seed = int(time.time() * 1000) ^ os.getpid()
70bcb6e @bdarnell Refactor random-seed code out of fork_processes
bdarnell authored
83 random.seed(seed)
84
85
3cf9098 @bdarnell Set CLOEXEC on subprocess pipe endpoints so they are not inherited by…
bdarnell authored
86 def _pipe_cloexec():
87 r, w = os.pipe()
88 set_close_exec(r)
89 set_close_exec(w)
90 return r, w
91
92
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
93 _task_id = None
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
94
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
95
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
96 def fork_processes(num_processes, max_restarts=100):
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
97 """Starts multiple worker processes.
98
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
99 If ``num_processes`` is None or <= 0, we detect the number of cores
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
100 available on this machine and fork that number of child
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
101 processes. If ``num_processes`` is given and > 0, we fork that
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
102 specific number of sub-processes.
103
104 Since we use processes and not threads, there is no shared memory
105 between any server code.
106
107 Note that multiple processes are not compatible with the autoreload
26cb426 @jniznan possibility to disable autoreload when debug is on
jniznan authored
108 module (or the ``autoreload=True`` option to `tornado.web.Application`
109 which defaults to True when ``debug=True``).
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
110 When using multiple processes, no IOLoops can be created or
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
111 referenced until after the call to ``fork_processes``.
112
113 In each child process, ``fork_processes`` returns its *task id*, a
114 number between 0 and ``num_processes``. Processes that exit
115 abnormally (due to a signal or non-zero exit status) are restarted
116 with the same id (up to ``max_restarts`` times). In the parent
117 process, ``fork_processes`` returns None if all child processes
118 have exited normally, but will otherwise only exit by throwing an
119 exception.
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
120 """
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
121 global _task_id
122 assert _task_id is None
6a7ff97 @bdarnell Pull process forking out of HTTPServer into a new module
bdarnell authored
123 if num_processes is None or num_processes <= 0:
124 num_processes = cpu_count()
125 if ioloop.IOLoop.initialized():
126 raise RuntimeError("Cannot run in multiple processes: IOLoop instance "
127 "has already been initialized. You cannot call "
128 "IOLoop.instance() before calling start_processes()")
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
129 gen_log.info("Starting %d processes", num_processes)
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
130 children = {}
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
131
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
132 def start_child(i):
133 pid = os.fork()
134 if pid == 0:
135 # child process
70bcb6e @bdarnell Refactor random-seed code out of fork_processes
bdarnell authored
136 _reseed_random()
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
137 global _task_id
138 _task_id = i
139 return i
140 else:
141 children[pid] = i
142 return None
143 for i in range(num_processes):
144 id = start_child(i)
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
145 if id is not None:
146 return id
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
147 num_restarts = 0
148 while children:
149 try:
150 pid, status = os.wait()
0d693ee @bdarnell Change "except Type, value" to "except Type as value".
bdarnell authored
151 except OSError as e:
f81a25e @cardoe define and use errno_from_exception abstraction
cardoe authored
152 if errno_from_exception(e) == errno.EINTR:
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
153 continue
154 raise
155 if pid not in children:
156 continue
157 id = children.pop(pid)
158 if os.WIFSIGNALED(status):
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
159 gen_log.warning("child %d (pid %d) killed by signal %d, restarting",
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
160 id, pid, os.WTERMSIG(status))
161 elif os.WEXITSTATUS(status) != 0:
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
162 gen_log.warning("child %d (pid %d) exited with status %d, restarting",
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
163 id, pid, os.WEXITSTATUS(status))
164 else:
9b944aa @bdarnell Switch from root logger to separate loggers.
bdarnell authored
165 gen_log.info("child %d (pid %d) exited normally", id, pid)
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
166 continue
167 num_restarts += 1
168 if num_restarts > max_restarts:
169 raise RuntimeError("Too many child restarts, giving up")
170 new_id = start_child(id)
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
171 if new_id is not None:
172 return new_id
4c23ba7 @bdarnell Make fork_processes raise SystemExit instead of returning None when
bdarnell authored
173 # All child processes exited cleanly, so exit the master process
174 # instead of just returning to right after the call to
175 # fork_processes (which will probably just start up another IOLoop
176 # unless the caller checks the return value).
177 sys.exit(0)
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
178
c152b78 @bdarnell While I'm touching every file, run autopep8 too.
bdarnell authored
179
5ef0487 @bdarnell Better fork_processes: Restart processes when they exit abnormally. …
bdarnell authored
180 def task_id():
181 """Returns the current task id, if any.
182
183 Returns None if this process was not created by `fork_processes`.
184 """
185 global _task_id
186 return _task_id
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
187
e582e58 @bdarnell Update autopep8 to 0.8.5 and run it.
bdarnell authored
188
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
189 class Subprocess(object):
190 """Wraps ``subprocess.Popen`` with IOStream support.
191
192 The constructor is the same as ``subprocess.Popen`` with the following
193 additions:
194
195 * ``stdin``, ``stdout``, and ``stderr`` may have the value
9f3dec0 @bdarnell Fix most of the rest of the dangling references.
bdarnell authored
196 ``tornado.process.Subprocess.STREAM``, which will make the corresponding
197 attribute of the resulting Subprocess a `.PipeIOStream`.
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
198 * A new keyword argument ``io_loop`` may be used to pass in an IOLoop.
ab6b980 @bdarnell A new IOLoop is automatically "current" if there isn't already one.
bdarnell authored
199
200 .. versionchanged:: 4.1
201 The ``io_loop`` argument is deprecated.
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
202 """
203 STREAM = object()
204
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
205 _initialized = False
206 _waiting = {}
207
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
208 def __init__(self, *args, **kwargs):
d088cb6 @bdarnell Fix default IOLoop in Subprocess.
bdarnell authored
209 self.io_loop = kwargs.pop('io_loop', None) or ioloop.IOLoop.current()
c82e97f @bdarnell Close pipe file descriptors if subprocess.Popen fails.
bdarnell authored
210 # All FDs we create should be closed on error; those in to_close
211 # should be closed in the parent process on success.
212 pipe_fds = []
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
213 to_close = []
214 if kwargs.get('stdin') is Subprocess.STREAM:
3cf9098 @bdarnell Set CLOEXEC on subprocess pipe endpoints so they are not inherited by…
bdarnell authored
215 in_r, in_w = _pipe_cloexec()
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
216 kwargs['stdin'] = in_r
c82e97f @bdarnell Close pipe file descriptors if subprocess.Popen fails.
bdarnell authored
217 pipe_fds.extend((in_r, in_w))
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
218 to_close.append(in_r)
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
219 self.stdin = PipeIOStream(in_w, io_loop=self.io_loop)
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
220 if kwargs.get('stdout') is Subprocess.STREAM:
3cf9098 @bdarnell Set CLOEXEC on subprocess pipe endpoints so they are not inherited by…
bdarnell authored
221 out_r, out_w = _pipe_cloexec()
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
222 kwargs['stdout'] = out_w
c82e97f @bdarnell Close pipe file descriptors if subprocess.Popen fails.
bdarnell authored
223 pipe_fds.extend((out_r, out_w))
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
224 to_close.append(out_w)
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
225 self.stdout = PipeIOStream(out_r, io_loop=self.io_loop)
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
226 if kwargs.get('stderr') is Subprocess.STREAM:
3cf9098 @bdarnell Set CLOEXEC on subprocess pipe endpoints so they are not inherited by…
bdarnell authored
227 err_r, err_w = _pipe_cloexec()
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
228 kwargs['stderr'] = err_w
c82e97f @bdarnell Close pipe file descriptors if subprocess.Popen fails.
bdarnell authored
229 pipe_fds.extend((err_r, err_w))
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
230 to_close.append(err_w)
08898b3 @martynsmith Fix typo in stream setup for stderr
martynsmith authored
231 self.stderr = PipeIOStream(err_r, io_loop=self.io_loop)
c82e97f @bdarnell Close pipe file descriptors if subprocess.Popen fails.
bdarnell authored
232 try:
233 self.proc = subprocess.Popen(*args, **kwargs)
234 except:
235 for fd in pipe_fds:
236 os.close(fd)
237 raise
4cf8344 @bdarnell Add tornado.process.Subprocess
bdarnell authored
238 for fd in to_close:
239 os.close(fd)
240 for attr in ['stdin', 'stdout', 'stderr', 'pid']:
241 if not hasattr(self, attr): # don't clobber streams set above
242 setattr(self, attr, getattr(self.proc, attr))
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
243 self._exit_callback = None
244 self.returncode = None
245
246 def set_exit_callback(self, callback):
247 """Runs ``callback`` when this process exits.
248
249 The callback takes one argument, the return code of the process.
250
1e1d61a @bdarnell s/SIGCHILD/SIGCHLD/ in docs.
bdarnell authored
251 This method uses a ``SIGCHLD`` handler, which is a global setting
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
252 and may conflict if you have other libraries trying to handle the
253 same signal. If you are using more than one ``IOLoop`` it may
254 be necessary to call `Subprocess.initialize` first to designate
255 one ``IOLoop`` to run the signal handlers.
256
257 In many cases a close callback on the stdout or stderr streams
258 can be used as an alternative to an exit callback if the
259 signal handler is causing a problem.
260 """
261 self._exit_callback = stack_context.wrap(callback)
262 Subprocess.initialize(self.io_loop)
263 Subprocess._waiting[self.pid] = self
264 Subprocess._try_cleanup_process(self.pid)
265
2bd2f0d @bdarnell Add raise_error behavior to Subprocess.wait_for_exit.
bdarnell authored
266 def wait_for_exit(self, raise_error=True):
2467782 @bdarnell Add Subprocess.wait_for_exit, a coro-friendly set_exit_callback().
bdarnell authored
267 """Returns a `.Future` which resolves when the process exits.
268
269 Usage::
270
271 ret = yield proc.wait_for_exit()
272
273 This is a coroutine-friendly alternative to `set_exit_callback`
274 (and a replacement for the blocking `subprocess.Popen.wait`).
275
2bd2f0d @bdarnell Add raise_error behavior to Subprocess.wait_for_exit.
bdarnell authored
276 By default, raises `subprocess.CalledProcessError` if the process
277 has a non-zero exit status. Use ``wait_for_exit(raise_error=False)``
278 to suppress this behavior and return the exit status without raising.
279
2467782 @bdarnell Add Subprocess.wait_for_exit, a coro-friendly set_exit_callback().
bdarnell authored
280 .. versionadded:: 4.2
281 """
282 future = Future()
2bd2f0d @bdarnell Add raise_error behavior to Subprocess.wait_for_exit.
bdarnell authored
283
284 def callback(ret):
285 if ret != 0 and raise_error:
286 # Unfortunately we don't have the original args any more.
287 future.set_exception(CalledProcessError(ret, None))
288 else:
289 future.set_result(ret)
290 self.set_exit_callback(callback)
2467782 @bdarnell Add Subprocess.wait_for_exit, a coro-friendly set_exit_callback().
bdarnell authored
291 return future
292
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
293 @classmethod
294 def initialize(cls, io_loop=None):
1e1d61a @bdarnell s/SIGCHILD/SIGCHLD/ in docs.
bdarnell authored
295 """Initializes the ``SIGCHLD`` handler.
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
296
693ebbe @bdarnell Doc updates, modules N-T.
bdarnell authored
297 The signal handler is run on an `.IOLoop` to avoid locking issues.
298 Note that the `.IOLoop` used for signal handling need not be the
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
299 same one used by individual Subprocess objects (as long as the
693ebbe @bdarnell Doc updates, modules N-T.
bdarnell authored
300 ``IOLoops`` are each running in separate threads).
ab6b980 @bdarnell A new IOLoop is automatically "current" if there isn't already one.
bdarnell authored
301
302 .. versionchanged:: 4.1
303 The ``io_loop`` argument is deprecated.
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
304 """
305 if cls._initialized:
306 return
307 if io_loop is None:
2ad9659 @bdarnell All functions that take an IOLoop default to current() instead of ins…
bdarnell authored
308 io_loop = ioloop.IOLoop.current()
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
309 cls._old_sigchld = signal.signal(
310 signal.SIGCHLD,
15d1746 @bdarnell Actually use add_callback_from_signal from the SIGCHLD handler.
bdarnell authored
311 lambda sig, frame: io_loop.add_callback_from_signal(cls._cleanup))
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
312 cls._initialized = True
313
314 @classmethod
315 def uninitialize(cls):
1e1d61a @bdarnell s/SIGCHILD/SIGCHLD/ in docs.
bdarnell authored
316 """Removes the ``SIGCHLD`` handler."""
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
317 if not cls._initialized:
318 return
319 signal.signal(signal.SIGCHLD, cls._old_sigchld)
320 cls._initialized = False
321
322 @classmethod
323 def _cleanup(cls):
900d5a2 @bdarnell Get all the tests passing under py3 without 2to3
bdarnell authored
324 for pid in list(cls._waiting.keys()): # make a copy
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
325 cls._try_cleanup_process(pid)
326
327 @classmethod
328 def _try_cleanup_process(cls, pid):
329 try:
330 ret_pid, status = os.waitpid(pid, os.WNOHANG)
0d693ee @bdarnell Change "except Type, value" to "except Type as value".
bdarnell authored
331 except OSError as e:
f81a25e @cardoe define and use errno_from_exception abstraction
cardoe authored
332 if errno_from_exception(e) == errno.ECHILD:
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
333 return
334 if ret_pid == 0:
335 return
336 assert ret_pid == pid
337 subproc = cls._waiting.pop(pid)
ae7cfe4 @bdarnell Add IOLoop.add_callback_from_signal, which avoids deadlocks
bdarnell authored
338 subproc.io_loop.add_callback_from_signal(
3c24b7b @bdarnell Remove some now-unnecessary calls to functools.partial
bdarnell authored
339 subproc._set_returncode, status)
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
340
200c607 @bdarnell Subprocess.returncode should match subprocess.Popen.returncode.
bdarnell authored
341 def _set_returncode(self, status):
342 if os.WIFSIGNALED(status):
343 self.returncode = -os.WTERMSIG(status)
344 else:
345 assert os.WIFEXITED(status)
346 self.returncode = os.WEXITSTATUS(status)
2a2b405 @bdarnell Add a SIGCHILD handler to Subprocess.
bdarnell authored
347 if self._exit_callback:
348 callback = self._exit_callback
349 self._exit_callback = None
200c607 @bdarnell Subprocess.returncode should match subprocess.Popen.returncode.
bdarnell authored
350 callback(self.returncode)
Something went wrong with that request. Please try again.