blob: 893507bcd5a4dd5bb6730c41ec1e084110bac5bb [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
Richard Oudkerk3e268aa2012-04-30 12:13:55 +01007# Licensed to PSF under a Contributor Agreement.
Benjamin Petersone711caf2008-06-11 16:44:04 +00008#
9
10__all__ = ['Process', 'current_process', 'active_children']
11
12#
13# Imports
14#
15
16import os
17import sys
18import signal
19import itertools
Antoine Pitrouc081c0c2011-07-15 22:12:24 +020020from _weakrefset import WeakSet
Benjamin Petersone711caf2008-06-11 16:44:04 +000021
22#
23#
24#
25
26try:
27 ORIGINAL_DIR = os.path.abspath(os.getcwd())
28except OSError:
29 ORIGINAL_DIR = None
30
Benjamin Petersone711caf2008-06-11 16:44:04 +000031#
32# Public functions
33#
34
35def current_process():
36 '''
37 Return process object representing the current process
38 '''
39 return _current_process
40
41def active_children():
42 '''
43 Return list of process objects corresponding to live child processes
44 '''
45 _cleanup()
46 return list(_current_process._children)
47
48#
49#
50#
51
52def _cleanup():
53 # check for processes which have finished
54 for p in list(_current_process._children):
55 if p._popen.poll() is not None:
56 _current_process._children.discard(p)
57
58#
59# The `Process` class
60#
61
62class Process(object):
63 '''
64 Process objects represent activity that is run in a separate process
65
66 The class is analagous to `threading.Thread`
67 '''
68 _Popen = None
69
Antoine Pitrou0bd4deb2011-02-25 22:07:43 +000070 def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
71 *, daemon=None):
Benjamin Petersone711caf2008-06-11 16:44:04 +000072 assert group is None, 'group argument must be None for now'
73 count = next(_current_process._counter)
74 self._identity = _current_process._identity + (count,)
75 self._authkey = _current_process._authkey
Antoine Pitrou0bd4deb2011-02-25 22:07:43 +000076 if daemon is not None:
77 self._daemonic = daemon
78 else:
79 self._daemonic = _current_process._daemonic
Benjamin Petersone711caf2008-06-11 16:44:04 +000080 self._tempdir = _current_process._tempdir
81 self._parent_pid = os.getpid()
82 self._popen = None
83 self._target = target
84 self._args = tuple(args)
85 self._kwargs = dict(kwargs)
86 self._name = name or type(self).__name__ + '-' + \
87 ':'.join(str(i) for i in self._identity)
Antoine Pitrouc081c0c2011-07-15 22:12:24 +020088 _dangling.add(self)
Benjamin Petersone711caf2008-06-11 16:44:04 +000089
90 def run(self):
91 '''
92 Method to be run in sub-process; can be overridden in sub-class
93 '''
94 if self._target:
95 self._target(*self._args, **self._kwargs)
96
97 def start(self):
98 '''
99 Start child process
100 '''
101 assert self._popen is None, 'cannot start a process twice'
102 assert self._parent_pid == os.getpid(), \
103 'can only start a process object created by current process'
104 assert not _current_process._daemonic, \
105 'daemonic processes are not allowed to have children'
106 _cleanup()
107 if self._Popen is not None:
108 Popen = self._Popen
109 else:
110 from .forking import Popen
111 self._popen = Popen(self)
Antoine Pitrou176f07d2011-06-06 19:35:31 +0200112 self._sentinel = self._popen.sentinel
Benjamin Petersone711caf2008-06-11 16:44:04 +0000113 _current_process._children.add(self)
114
115 def terminate(self):
116 '''
117 Terminate process; sends SIGTERM signal or uses TerminateProcess()
118 '''
119 self._popen.terminate()
120
121 def join(self, timeout=None):
122 '''
123 Wait until child process terminates
124 '''
125 assert self._parent_pid == os.getpid(), 'can only join a child process'
126 assert self._popen is not None, 'can only join a started process'
127 res = self._popen.wait(timeout)
128 if res is not None:
129 _current_process._children.discard(self)
130
131 def is_alive(self):
132 '''
133 Return whether process is alive
134 '''
135 if self is _current_process:
136 return True
137 assert self._parent_pid == os.getpid(), 'can only test a child process'
138 if self._popen is None:
139 return False
140 self._popen.poll()
141 return self._popen.returncode is None
142
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000143 @property
144 def name(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000145 return self._name
146
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000147 @name.setter
148 def name(self, name):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000149 assert isinstance(name, str), 'name must be a string'
150 self._name = name
151
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000152 @property
153 def daemon(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000154 '''
155 Return whether process is a daemon
156 '''
157 return self._daemonic
158
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000159 @daemon.setter
160 def daemon(self, daemonic):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000161 '''
162 Set whether process is a daemon
163 '''
164 assert self._popen is None, 'process has already started'
165 self._daemonic = daemonic
166
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000167 @property
168 def authkey(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000169 return self._authkey
170
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000171 @authkey.setter
172 def authkey(self, authkey):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000173 '''
174 Set authorization key of process
175 '''
176 self._authkey = AuthenticationString(authkey)
177
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000178 @property
179 def exitcode(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000180 '''
181 Return exit code of process or `None` if it has yet to stop
182 '''
183 if self._popen is None:
184 return self._popen
185 return self._popen.poll()
186
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000187 @property
188 def ident(self):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000189 '''
Florent Xiclunab519d232010-03-04 16:10:55 +0000190 Return identifier (PID) of process or `None` if it has yet to start
Benjamin Petersone711caf2008-06-11 16:44:04 +0000191 '''
192 if self is _current_process:
193 return os.getpid()
194 else:
195 return self._popen and self._popen.pid
196
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000197 pid = ident
Benjamin Petersone711caf2008-06-11 16:44:04 +0000198
Antoine Pitrou176f07d2011-06-06 19:35:31 +0200199 @property
200 def sentinel(self):
201 '''
202 Return a file descriptor (Unix) or handle (Windows) suitable for
203 waiting for process termination.
204 '''
205 try:
206 return self._sentinel
207 except AttributeError:
208 raise ValueError("process not started")
209
Benjamin Petersone711caf2008-06-11 16:44:04 +0000210 def __repr__(self):
211 if self is _current_process:
212 status = 'started'
213 elif self._parent_pid != os.getpid():
214 status = 'unknown'
215 elif self._popen is None:
216 status = 'initial'
217 else:
218 if self._popen.poll() is not None:
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000219 status = self.exitcode
Benjamin Petersone711caf2008-06-11 16:44:04 +0000220 else:
221 status = 'started'
222
223 if type(status) is int:
224 if status == 0:
225 status = 'stopped'
226 else:
227 status = 'stopped[%s]' % _exitcode_to_name.get(status, status)
228
229 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name,
230 status, self._daemonic and ' daemon' or '')
231
232 ##
233
234 def _bootstrap(self):
235 from . import util
236 global _current_process
237
238 try:
239 self._children = set()
240 self._counter = itertools.count(1)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000241 if sys.stdin is not None:
242 try:
Alexandre Vassalottic57a84f2009-07-17 12:07:01 +0000243 sys.stdin.close()
244 sys.stdin = open(os.devnull)
Amaury Forgeot d'Arc768008c2008-08-20 09:04:46 +0000245 except (OSError, ValueError):
246 pass
Victor Stinner0f83b152011-06-17 12:31:49 +0200247 old_process = _current_process
Benjamin Petersone711caf2008-06-11 16:44:04 +0000248 _current_process = self
Victor Stinner0f83b152011-06-17 12:31:49 +0200249 try:
250 util._finalizer_registry.clear()
251 util._run_after_forkers()
252 finally:
253 # delay finalization of the old process object until after
254 # _run_after_forkers() is executed
255 del old_process
Benjamin Petersone711caf2008-06-11 16:44:04 +0000256 util.info('child process calling self.run()')
257 try:
258 self.run()
259 exitcode = 0
260 finally:
261 util._exit_function()
262 except SystemExit as e:
263 if not e.args:
264 exitcode = 1
Richard Oudkerk29471de2012-06-06 19:04:57 +0100265 elif isinstance(e.args[0], int):
Benjamin Petersone711caf2008-06-11 16:44:04 +0000266 exitcode = e.args[0]
267 else:
Richard Oudkerk29471de2012-06-06 19:04:57 +0100268 sys.stderr.write(str(e.args[0]) + '\n')
269 exitcode = 0 if isinstance(e.args[0], str) else 1
Benjamin Petersone711caf2008-06-11 16:44:04 +0000270 except:
271 exitcode = 1
272 import traceback
Benjamin Peterson58ea9fe2008-08-19 19:17:39 +0000273 sys.stderr.write('Process %s:\n' % self.name)
Benjamin Petersone711caf2008-06-11 16:44:04 +0000274 traceback.print_exc()
Antoine Pitrou84a0fbf2012-01-27 10:52:37 +0100275 finally:
276 util.info('process exiting with exitcode %d' % exitcode)
277 sys.stdout.flush()
278 sys.stderr.flush()
Benjamin Petersone711caf2008-06-11 16:44:04 +0000279
Benjamin Petersone711caf2008-06-11 16:44:04 +0000280 return exitcode
281
282#
283# We subclass bytes to avoid accidental transmission of auth keys over network
284#
285
286class AuthenticationString(bytes):
287 def __reduce__(self):
288 from .forking import Popen
289 if not Popen.thread_is_spawning():
290 raise TypeError(
291 'Pickling an AuthenticationString object is '
292 'disallowed for security reasons'
293 )
294 return AuthenticationString, (bytes(self),)
295
296#
297# Create object representing the main process
298#
299
300class _MainProcess(Process):
301
302 def __init__(self):
303 self._identity = ()
304 self._daemonic = False
305 self._name = 'MainProcess'
306 self._parent_pid = None
307 self._popen = None
308 self._counter = itertools.count(1)
309 self._children = set()
310 self._authkey = AuthenticationString(os.urandom(32))
311 self._tempdir = None
312
313_current_process = _MainProcess()
314del _MainProcess
315
316#
317# Give names to some return codes
318#
319
320_exitcode_to_name = {}
321
322for name, signum in list(signal.__dict__.items()):
323 if name[:3]=='SIG' and '_' not in name:
324 _exitcode_to_name[-signum] = name
Antoine Pitrouc081c0c2011-07-15 22:12:24 +0200325
326# For debug and leak testing
327_dangling = WeakSet()