blob: b599f11606ef835811385cc6d053d21dcefb6ad1 [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
Antoine Pitrou0bd4deb2011-02-25 22:07:43 +000095 def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
96 *, daemon=None):
Benjamin Petersone711caf2008-06-11 16:44:04 +000097 assert group is None, 'group argument must be None for now'
98 count = next(_current_process._counter)
99 self._identity = _current_process._identity + (count,)
100 self._authkey = _current_process._authkey
Antoine Pitrou0bd4deb2011-02-25 22:07:43 +0000101 if daemon is not None:
102 self._daemonic = daemon
103 else:
104 self._daemonic = _current_process._daemonic
Benjamin Petersone711caf2008-06-11 16:44:04 +0000105 self._tempdir = _current_process._tempdir
106 self._parent_pid = os.getpid()
107 self._popen = None
108 self._target = target
109 self._args = tuple(args)
110 self._kwargs = dict(kwargs)
111 self._name = name or type(self).__name__ + '-' + \
112 ':'.join(str(i) for i in self._identity)
Antoine Pitrouc081c0c2011-07-15 22:12:24 +0200113 _dangling.add(self)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000114
115 def run(self):
116 '''
117 Method to be run in sub-process; can be overridden in sub-class
118 '''
119 if self._target:
120 self._target(*self._args, **self._kwargs)
121
122 def start(self):
123 '''
124 Start child process
125 '''
126 assert self._popen is None, 'cannot start a process twice'
127 assert self._parent_pid == os.getpid(), \
128 'can only start a process object created by current process'
129 assert not _current_process._daemonic, \
130 'daemonic processes are not allowed to have children'
131 _cleanup()
132 if self._Popen is not None:
133 Popen = self._Popen
134 else:
135 from .forking import Popen
136 self._popen = Popen(self)
Antoine Pitrou176f07d2011-06-06 19:35:31 +0200137 self._sentinel = self._popen.sentinel
Benjamin Petersone711caf2008-06-11 16:44:04 +0000138 _current_process._children.add(self)
139
140 def terminate(self):
141 '''
142 Terminate process; sends SIGTERM signal or uses TerminateProcess()
143 '''
144 self._popen.terminate()
145
146 def join(self, timeout=None):
147 '''
148 Wait until child process terminates
149 '''
150 assert self._parent_pid == os.getpid(), 'can only join a child process'
151 assert self._popen is not None, 'can only join a started process'
152 res = self._popen.wait(timeout)
153 if res is not None:
154 _current_process._children.discard(self)
155
156 def is_alive(self):
157 '''
158 Return whether process is alive
159 '''
160 if self is _current_process:
161 return True
162 assert self._parent_pid == os.getpid(), 'can only test a child process'
163 if self._popen is None:
164 return False
165 self._popen.poll()
166 return self._popen.returncode is None
167
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000168 @property
169 def name(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000170 return self._name
171
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000172 @name.setter
173 def name(self, name):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000174 assert isinstance(name, str), 'name must be a string'
175 self._name = name
176
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000177 @property
178 def daemon(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000179 '''
180 Return whether process is a daemon
181 '''
182 return self._daemonic
183
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000184 @daemon.setter
185 def daemon(self, daemonic):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000186 '''
187 Set whether process is a daemon
188 '''
189 assert self._popen is None, 'process has already started'
190 self._daemonic = daemonic
191
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000192 @property
193 def authkey(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000194 return self._authkey
195
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000196 @authkey.setter
197 def authkey(self, authkey):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000198 '''
199 Set authorization key of process
200 '''
201 self._authkey = AuthenticationString(authkey)
202
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000203 @property
204 def exitcode(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000205 '''
206 Return exit code of process or `None` if it has yet to stop
207 '''
208 if self._popen is None:
209 return self._popen
210 return self._popen.poll()
211
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000212 @property
213 def ident(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000214 '''
Florent Xiclunab519d232010-03-04 16:10:55 +0000215 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Petersone711caf2008-06-11 16:44:04 +0000216 '''
217 if self is _current_process:
218 return os.getpid()
219 else:
220 return self._popen and self._popen.pid
221
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000222 pid = ident
Benjamin Petersone711caf2008-06-11 16:44:04 +0000223
Antoine Pitrou176f07d2011-06-06 19:35:31 +0200224 @property
225 def sentinel(self):
226 '''
227 Return a file descriptor (Unix) or handle (Windows) suitable for
228 waiting for process termination.
229 '''
230 try:
231 return self._sentinel
232 except AttributeError:
233 raise ValueError("process not started")
234
Benjamin Petersone711caf2008-06-11 16:44:04 +0000235 def __repr__(self):
236 if self is _current_process:
237 status = 'started'
238 elif self._parent_pid != os.getpid():
239 status = 'unknown'
240 elif self._popen is None:
241 status = 'initial'
242 else:
243 if self._popen.poll() is not None:
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000244 status = self.exitcode
Benjamin Petersone711caf2008-06-11 16:44:04 +0000245 else:
246 status = 'started'
247
248 if type(status) is int:
249 if status == 0:
250 status = 'stopped'
251 else:
252 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
253
254 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
255 status, self._daemonic and ' daemon' or '')
256
257 ##
258
259 def _bootstrap(self):
260 from . import util
261 global _current_process
262
263 try:
264 self._children = set()
265 self._counter = itertools.count(1)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000266 if sys.stdin is not None:
267 try:
Alexandre Vassalottic57a84f2009-07-17 12:07:01 +0000268 sys.stdin.close()
269 sys.stdin = open(os.devnull)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000270 except (OSError, ValueError):
271 pass
Victor Stinner0f83b152011-06-17 12:31:49 +0200272 old_process = _current_process
Benjamin Petersone711caf2008-06-11 16:44:04 +0000273 _current_process = self
Victor Stinner0f83b152011-06-17 12:31:49 +0200274 try:
275 util._finalizer_registry.clear()
276 util._run_after_forkers()
277 finally:
278 # delay finalization of the old process object until after
279 # _run_after_forkers() is executed
280 del old_process
Benjamin Petersone711caf2008-06-11 16:44:04 +0000281 util.info('child process calling self.run()')
282 try:
283 self.run()
284 exitcode = 0
285 finally:
286 util._exit_function()
287 except SystemExit as e:
288 if not e.args:
289 exitcode = 1
290 elif type(e.args[0]) is int:
291 exitcode = e.args[0]
292 else:
293 sys.stderr.write(e.args[0] + '\n')
Benjamin Petersone711caf2008-06-11 16:44:04 +0000294 exitcode = 1
295 except:
296 exitcode = 1
297 import traceback
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000298 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000299 traceback.print_exc()
Antoine Pitrou84a0fbf2012-01-27 10:52:37 +0100300 finally:
301 util.info('process exiting with exitcode %d' % exitcode)
302 sys.stdout.flush()
303 sys.stderr.flush()
Benjamin Petersone711caf2008-06-11 16:44:04 +0000304
Benjamin Petersone711caf2008-06-11 16:44:04 +0000305 return exitcode
306
307#
308# We subclass bytes to avoid accidental transmission of auth keys over network
309#
310
311class AuthenticationString(bytes):
312 def __reduce__(self):
313 from .forking import Popen
314 if not Popen.thread_is_spawning():
315 raise TypeError(
316 'Pickling an AuthenticationString object is '
317 'disallowed for security reasons'
318 )
319 return AuthenticationString, (bytes(self),)
320
321#
322# Create object representing the main process
323#
324
325class _MainProcess(Process):
326
327 def __init__(self):
328 self._identity = ()
329 self._daemonic = False
330 self._name = 'MainProcess'
331 self._parent_pid = None
332 self._popen = None
333 self._counter = itertools.count(1)
334 self._children = set()
335 self._authkey = AuthenticationString(os.urandom(32))
336 self._tempdir = None
337
338_current_process = _MainProcess()
339del _MainProcess
340
341#
342# Give names to some return codes
343#
344
345_exitcode_to_name = {}
346
347for name, signum in list(signal.__dict__.items()):
348 if name[:3]=='SIG' and '_' not in name:
349 _exitcode_to_name[-signum] = name
Antoine Pitrouc081c0c2011-07-15 22:12:24 +0200350
351# For debug and leak testing
352_dangling = WeakSet()