blob: 482ea0a37a83bc6862449fa3cf3d47c3c004ad94 [file] [log] [blame]
Benjamin Peterson7f03ea72008-06-13 19:20:48 +00001#
2# Module providing the `Process` class which emulates `threading.Thread`
3#
4# multiprocessing/process.py
5#
R. David Murray79af2452010-12-14 01:42:40 +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 Peterson7f03ea72008-06-13 19:20:48 +000033#
34
35__all__ = ['Process', 'current_process', 'active_children']
36
37#
38# Imports
39#
40
41import os
42import sys
43import signal
44import itertools
45
46#
47#
48#
49
50try:
51 ORIGINAL_DIR = os.path.abspath(os.getcwd())
52except OSError:
53 ORIGINAL_DIR = None
54
Benjamin Peterson7f03ea72008-06-13 19:20:48 +000055#
56# Public functions
57#
58
59def current_process():
60 '''
61 Return process object representing the current process
62 '''
63 return _current_process
64
65def active_children():
66 '''
67 Return list of process objects corresponding to live child processes
68 '''
69 _cleanup()
70 return list(_current_process._children)
71
72#
73#
74#
75
76def _cleanup():
77 # check for processes which have finished
78 for p in list(_current_process._children):
79 if p._popen.poll() is not None:
80 _current_process._children.discard(p)
81
82#
83# The `Process` class
84#
85
86class Process(object):
87 '''
88 Process objects represent activity that is run in a separate process
89
90 The class is analagous to `threading.Thread`
91 '''
92 _Popen = None
93
94 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
95 assert group is None, 'group argument must be None for now'
96 count = _current_process._counter.next()
97 self._identity = _current_process._identity + (count,)
98 self._authkey = _current_process._authkey
99 self._daemonic = _current_process._daemonic
100 self._tempdir = _current_process._tempdir
101 self._parent_pid = os.getpid()
102 self._popen = None
103 self._target = target
104 self._args = tuple(args)
105 self._kwargs = dict(kwargs)
106 self._name = name or type(self).__name__ + '-' + \
107 ':'.join(str(i) for i in self._identity)
108
109 def run(self):
110 '''
111 Method to be run in sub-process; can be overridden in sub-class
112 '''
113 if self._target:
114 self._target(*self._args, **self._kwargs)
115
116 def start(self):
117 '''
118 Start child process
119 '''
120 assert self._popen is None, 'cannot start a process twice'
121 assert self._parent_pid == os.getpid(), \
122 'can only start a process object created by current process'
123 assert not _current_process._daemonic, \
124 'daemonic processes are not allowed to have children'
125 _cleanup()
126 if self._Popen is not None:
127 Popen = self._Popen
128 else:
129 from .forking import Popen
130 self._popen = Popen(self)
131 _current_process._children.add(self)
132
133 def terminate(self):
134 '''
135 Terminate process; sends SIGTERM signal or uses TerminateProcess()
136 '''
137 self._popen.terminate()
138
139 def join(self, timeout=None):
140 '''
141 Wait until child process terminates
142 '''
143 assert self._parent_pid == os.getpid(), 'can only join a child process'
144 assert self._popen is not None, 'can only join a started process'
145 res = self._popen.wait(timeout)
146 if res is not None:
147 _current_process._children.discard(self)
148
149 def is_alive(self):
150 '''
151 Return whether process is alive
152 '''
153 if self is _current_process:
154 return True
155 assert self._parent_pid == os.getpid(), 'can only test a child process'
156 if self._popen is None:
157 return False
158 self._popen.poll()
159 return self._popen.returncode is None
160
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000161 @property
162 def name(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000163 return self._name
164
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000165 @name.setter
166 def name(self, name):
Benjamin Peterson27cae342009-12-24 15:19:40 +0000167 assert isinstance(name, basestring), 'name must be a string'
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000168 self._name = name
169
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000170 @property
171 def daemon(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000172 '''
173 Return whether process is a daemon
174 '''
175 return self._daemonic
176
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000177 @daemon.setter
178 def daemon(self, daemonic):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000179 '''
180 Set whether process is a daemon
181 '''
182 assert self._popen is None, 'process has already started'
183 self._daemonic = daemonic
184
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000185 @property
186 def authkey(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000187 return self._authkey
188
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000189 @authkey.setter
190 def authkey(self, authkey):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000191 '''
192 Set authorization key of process
193 '''
194 self._authkey = AuthenticationString(authkey)
195
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000196 @property
197 def exitcode(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000198 '''
199 Return exit code of process or `None` if it has yet to stop
200 '''
201 if self._popen is None:
202 return self._popen
203 return self._popen.poll()
204
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000205 @property
206 def ident(self):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000207 '''
Florent Xicluna3bc5cb72010-03-04 15:58:54 +0000208 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000209 '''
210 if self is _current_process:
211 return os.getpid()
212 else:
213 return self._popen and self._popen.pid
214
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000215 pid = ident
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000216
217 def __repr__(self):
218 if self is _current_process:
219 status = 'started'
220 elif self._parent_pid != os.getpid():
221 status = 'unknown'
222 elif self._popen is None:
223 status = 'initial'
224 else:
225 if self._popen.poll() is not None:
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000226 status = self.exitcode
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000227 else:
228 status = 'started'
229
230 if type(status) is int:
231 if status == 0:
232 status = 'stopped'
233 else:
234 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
235
236 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
237 status, self._daemonic and ' daemon' or '')
238
239 ##
240
241 def _bootstrap(self):
242 from . import util
243 global _current_process
244
245 try:
246 self._children = set()
247 self._counter = itertools.count(1)
248 try:
Jesse Noller1b90efb2009-06-30 17:11:52 +0000249 sys.stdin.close()
250 sys.stdin = open(os.devnull)
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000251 except (OSError, ValueError):
252 pass
253 _current_process = self
254 util._finalizer_registry.clear()
255 util._run_after_forkers()
256 util.info('child process calling self.run()')
257 try:
258 self.run()
259 exitcode = 0
260 finally:
261 util._exit_function()
262 except SystemExit, e:
263 if not e.args:
264 exitcode = 1
Richard Oudkerk2182e052012-06-06 19:01:14 +0100265 elif isinstance(e.args[0], int):
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000266 exitcode = e.args[0]
267 else:
Richard Oudkerk2182e052012-06-06 19:01:14 +0100268 sys.stderr.write(str(e.args[0]) + '\n')
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000269 sys.stderr.flush()
Richard Oudkerk2182e052012-06-06 19:01:14 +0100270 exitcode = 0 if isinstance(e.args[0], str) else 1
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000271 except:
272 exitcode = 1
273 import traceback
Jesse Noller5bc9f4c2008-08-19 19:06:19 +0000274 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Peterson7f03ea72008-06-13 19:20:48 +0000275 sys.stderr.flush()
276 traceback.print_exc()
277
278 util.info('process exiting with exitcode %d' % exitcode)
279 return exitcode
280
281#
282# We subclass bytes to avoid accidental transmission of auth keys over network
283#
284
285class AuthenticationString(bytes):
286 def __reduce__(self):
287 from .forking import Popen
288 if not Popen.thread_is_spawning():
289 raise TypeError(
290 'Pickling an AuthenticationString object is '
291 'disallowed for security reasons'
292 )
293 return AuthenticationString, (bytes(self),)
294
295#
296# Create object representing the main process
297#
298
299class _MainProcess(Process):
300
301 def __init__(self):
302 self._identity = ()
303 self._daemonic = False
304 self._name = 'MainProcess'
305 self._parent_pid = None
306 self._popen = None
307 self._counter = itertools.count(1)
308 self._children = set()
309 self._authkey = AuthenticationString(os.urandom(32))
310 self._tempdir = None
311
312_current_process = _MainProcess()
313del _MainProcess
314
315#
316# Give names to some return codes
317#
318
319_exitcode_to_name = {}
320
321for name, signum in signal.__dict__.items():
322 if name[:3]=='SIG' and '_' not in name:
323 _exitcode_to_name[-signum] = name