blob: 2b61ee9a0d39a2d154975f2a48f16ae5a2da80f3 [file] [log] [blame]
Benjamin Petersone711caf2008-06-11 16:44:04 +00001#
2# Module providing the `Process` class which emulates `threading.Thread`
3#
4# multiprocessing/process.py
5#
R. David Murray3fc969a2010-12-14 01:38:16 +00006# Copyright (c) 2006-2008, R Oudkerk
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12#
13# 1. Redistributions of source code must retain the above copyright
14# notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16# notice, this list of conditions and the following disclaimer in the
17# documentation and/or other materials provided with the distribution.
18# 3. Neither the name of author nor the names of any contributors may be
19# used to endorse or promote products derived from this software
20# without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
23# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32# SUCH DAMAGE.
Benjamin Petersone711caf2008-06-11 16:44:04 +000033#
34
35__all__ = ['Process', 'current_process', 'active_children']
36
37#
38# Imports
39#
40
41import os
42import sys
43import signal
44import itertools
Antoine Pitrouc081c0c2011-07-15 22:12:24 +020045from _weakrefset import WeakSet
Benjamin Petersone711caf2008-06-11 16:44:04 +000046
47#
48#
49#
50
51try:
52 ORIGINAL_DIR = os.path.abspath(os.getcwd())
53except OSError:
54 ORIGINAL_DIR = None
55
Benjamin Petersone711caf2008-06-11 16:44:04 +000056#
57# Public functions
58#
59
60def current_process():
61 '''
62 Return process object representing the current process
63 '''
64 return _current_process
65
66def active_children():
67 '''
68 Return list of process objects corresponding to live child processes
69 '''
70 _cleanup()
71 return list(_current_process._children)
72
73#
74#
75#
76
77def _cleanup():
78 # check for processes which have finished
79 for p in list(_current_process._children):
80 if p._popen.poll() is not None:
81 _current_process._children.discard(p)
82
83#
84# The `Process` class
85#
86
87class Process(object):
88 '''
89 Process objects represent activity that is run in a separate process
90
91 The class is analagous to `threading.Thread`
92 '''
93 _Popen = None
94
95 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
96 assert group is None, 'group argument must be None for now'
97 count = next(_current_process._counter)
98 self._identity = _current_process._identity + (count,)
99 self._authkey = _current_process._authkey
100 self._daemonic = _current_process._daemonic
101 self._tempdir = _current_process._tempdir
102 self._parent_pid = os.getpid()
103 self._popen = None
104 self._target = target
105 self._args = tuple(args)
106 self._kwargs = dict(kwargs)
107 self._name = name or type(self).__name__ + '-' + \
108 ':'.join(str(i) for i in self._identity)
Antoine Pitrouc081c0c2011-07-15 22:12:24 +0200109 _dangling.add(self)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000110
111 def run(self):
112 '''
113 Method to be run in sub-process; can be overridden in sub-class
114 '''
115 if self._target:
116 self._target(*self._args, **self._kwargs)
117
118 def start(self):
119 '''
120 Start child process
121 '''
122 assert self._popen is None, 'cannot start a process twice'
123 assert self._parent_pid == os.getpid(), \
124 'can only start a process object created by current process'
125 assert not _current_process._daemonic, \
126 'daemonic processes are not allowed to have children'
127 _cleanup()
128 if self._Popen is not None:
129 Popen = self._Popen
130 else:
131 from .forking import Popen
132 self._popen = Popen(self)
133 _current_process._children.add(self)
134
135 def terminate(self):
136 '''
137 Terminate process; sends SIGTERM signal or uses TerminateProcess()
138 '''
139 self._popen.terminate()
140
141 def join(self, timeout=None):
142 '''
143 Wait until child process terminates
144 '''
145 assert self._parent_pid == os.getpid(), 'can only join a child process'
146 assert self._popen is not None, 'can only join a started process'
147 res = self._popen.wait(timeout)
148 if res is not None:
149 _current_process._children.discard(self)
150
151 def is_alive(self):
152 '''
153 Return whether process is alive
154 '''
155 if self is _current_process:
156 return True
157 assert self._parent_pid == os.getpid(), 'can only test a child process'
158 if self._popen is None:
159 return False
160 self._popen.poll()
161 return self._popen.returncode is None
162
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000163 @property
164 def name(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000165 return self._name
166
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000167 @name.setter
168 def name(self, name):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000169 assert isinstance(name, str), 'name must be a string'
170 self._name = name
171
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000172 @property
173 def daemon(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000174 '''
175 Return whether process is a daemon
176 '''
177 return self._daemonic
178
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000179 @daemon.setter
180 def daemon(self, daemonic):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000181 '''
182 Set whether process is a daemon
183 '''
184 assert self._popen is None, 'process has already started'
185 self._daemonic = daemonic
186
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000187 @property
188 def authkey(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000189 return self._authkey
190
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000191 @authkey.setter
192 def authkey(self, authkey):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000193 '''
194 Set authorization key of process
195 '''
196 self._authkey = AuthenticationString(authkey)
197
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000198 @property
199 def exitcode(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000200 '''
201 Return exit code of process or `None` if it has yet to stop
202 '''
203 if self._popen is None:
204 return self._popen
205 return self._popen.poll()
206
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000207 @property
208 def ident(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000209 '''
Florent Xiclunab519d232010-03-04 16:10:55 +0000210 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Petersone711caf2008-06-11 16:44:04 +0000211 '''
212 if self is _current_process:
213 return os.getpid()
214 else:
215 return self._popen and self._popen.pid
216
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000217 pid = ident
Benjamin Petersone711caf2008-06-11 16:44:04 +0000218
219 def __repr__(self):
220 if self is _current_process:
221 status = 'started'
222 elif self._parent_pid != os.getpid():
223 status = 'unknown'
224 elif self._popen is None:
225 status = 'initial'
226 else:
227 if self._popen.poll() is not None:
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000228 status = self.exitcode
Benjamin Petersone711caf2008-06-11 16:44:04 +0000229 else:
230 status = 'started'
231
232 if type(status) is int:
233 if status == 0:
234 status = 'stopped'
235 else:
236 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
237
238 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
239 status, self._daemonic and ' daemon' or '')
240
241 ##
242
243 def _bootstrap(self):
244 from . import util
245 global _current_process
246
247 try:
248 self._children = set()
249 self._counter = itertools.count(1)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000250 if sys.stdin is not None:
251 try:
Alexandre Vassalottic57a84f2009-07-17 12:07:01 +0000252 sys.stdin.close()
253 sys.stdin = open(os.devnull)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000254 except (OSError, ValueError):
255 pass
Victor Stinner0f83b152011-06-17 12:31:49 +0200256 old_process = _current_process
Benjamin Petersone711caf2008-06-11 16:44:04 +0000257 _current_process = self
Victor Stinner0f83b152011-06-17 12:31:49 +0200258 try:
259 util._finalizer_registry.clear()
260 util._run_after_forkers()
261 finally:
262 # delay finalization of the old process object until after
263 # _run_after_forkers() is executed
264 del old_process
Benjamin Petersone711caf2008-06-11 16:44:04 +0000265 util.info('child process calling self.run()')
266 try:
267 self.run()
268 exitcode = 0
269 finally:
270 util._exit_function()
271 except SystemExit as e:
272 if not e.args:
273 exitcode = 1
274 elif type(e.args[0]) is int:
275 exitcode = e.args[0]
276 else:
277 sys.stderr.write(e.args[0] + '\n')
Benjamin Petersone711caf2008-06-11 16:44:04 +0000278 exitcode = 1
279 except:
280 exitcode = 1
281 import traceback
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000282 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000283 traceback.print_exc()
Antoine Pitrou84a0fbf2012-01-27 10:52:37 +0100284 finally:
285 util.info('process exiting with exitcode %d' % exitcode)
286 sys.stdout.flush()
287 sys.stderr.flush()
Benjamin Petersone711caf2008-06-11 16:44:04 +0000288
Benjamin Petersone711caf2008-06-11 16:44:04 +0000289 return exitcode
290
291#
292# We subclass bytes to avoid accidental transmission of auth keys over network
293#
294
295class AuthenticationString(bytes):
296 def __reduce__(self):
297 from .forking import Popen
298 if not Popen.thread_is_spawning():
299 raise TypeError(
300 'Pickling an AuthenticationString object is '
301 'disallowed for security reasons'
302 )
303 return AuthenticationString, (bytes(self),)
304
305#
306# Create object representing the main process
307#
308
309class _MainProcess(Process):
310
311 def __init__(self):
312 self._identity = ()
313 self._daemonic = False
314 self._name = 'MainProcess'
315 self._parent_pid = None
316 self._popen = None
317 self._counter = itertools.count(1)
318 self._children = set()
319 self._authkey = AuthenticationString(os.urandom(32))
320 self._tempdir = None
321
322_current_process = _MainProcess()
323del _MainProcess
324
325#
326# Give names to some return codes
327#
328
329_exitcode_to_name = {}
330
331for name, signum in list(signal.__dict__.items()):
332 if name[:3]=='SIG' and '_' not in name:
333 _exitcode_to_name[-signum] = name
Antoine Pitrouc081c0c2011-07-15 22:12:24 +0200334
335# For debug and leak testing
336_dangling = WeakSet()