jpayne@69
|
1 #
|
jpayne@69
|
2 # Module providing the `Process` class which emulates `threading.Thread`
|
jpayne@69
|
3 #
|
jpayne@69
|
4 # multiprocessing/process.py
|
jpayne@69
|
5 #
|
jpayne@69
|
6 # Copyright (c) 2006-2008, R Oudkerk
|
jpayne@69
|
7 # Licensed to PSF under a Contributor Agreement.
|
jpayne@69
|
8 #
|
jpayne@69
|
9
|
jpayne@69
|
10 __all__ = ['BaseProcess', 'current_process', 'active_children',
|
jpayne@69
|
11 'parent_process']
|
jpayne@69
|
12
|
jpayne@69
|
13 #
|
jpayne@69
|
14 # Imports
|
jpayne@69
|
15 #
|
jpayne@69
|
16
|
jpayne@69
|
17 import os
|
jpayne@69
|
18 import sys
|
jpayne@69
|
19 import signal
|
jpayne@69
|
20 import itertools
|
jpayne@69
|
21 import threading
|
jpayne@69
|
22 from _weakrefset import WeakSet
|
jpayne@69
|
23
|
jpayne@69
|
24 #
|
jpayne@69
|
25 #
|
jpayne@69
|
26 #
|
jpayne@69
|
27
|
jpayne@69
|
28 try:
|
jpayne@69
|
29 ORIGINAL_DIR = os.path.abspath(os.getcwd())
|
jpayne@69
|
30 except OSError:
|
jpayne@69
|
31 ORIGINAL_DIR = None
|
jpayne@69
|
32
|
jpayne@69
|
33 #
|
jpayne@69
|
34 # Public functions
|
jpayne@69
|
35 #
|
jpayne@69
|
36
|
jpayne@69
|
37 def current_process():
|
jpayne@69
|
38 '''
|
jpayne@69
|
39 Return process object representing the current process
|
jpayne@69
|
40 '''
|
jpayne@69
|
41 return _current_process
|
jpayne@69
|
42
|
jpayne@69
|
43 def active_children():
|
jpayne@69
|
44 '''
|
jpayne@69
|
45 Return list of process objects corresponding to live child processes
|
jpayne@69
|
46 '''
|
jpayne@69
|
47 _cleanup()
|
jpayne@69
|
48 return list(_children)
|
jpayne@69
|
49
|
jpayne@69
|
50
|
jpayne@69
|
51 def parent_process():
|
jpayne@69
|
52 '''
|
jpayne@69
|
53 Return process object representing the parent process
|
jpayne@69
|
54 '''
|
jpayne@69
|
55 return _parent_process
|
jpayne@69
|
56
|
jpayne@69
|
57 #
|
jpayne@69
|
58 #
|
jpayne@69
|
59 #
|
jpayne@69
|
60
|
jpayne@69
|
61 def _cleanup():
|
jpayne@69
|
62 # check for processes which have finished
|
jpayne@69
|
63 for p in list(_children):
|
jpayne@69
|
64 if p._popen.poll() is not None:
|
jpayne@69
|
65 _children.discard(p)
|
jpayne@69
|
66
|
jpayne@69
|
67 #
|
jpayne@69
|
68 # The `Process` class
|
jpayne@69
|
69 #
|
jpayne@69
|
70
|
jpayne@69
|
71 class BaseProcess(object):
|
jpayne@69
|
72 '''
|
jpayne@69
|
73 Process objects represent activity that is run in a separate process
|
jpayne@69
|
74
|
jpayne@69
|
75 The class is analogous to `threading.Thread`
|
jpayne@69
|
76 '''
|
jpayne@69
|
77 def _Popen(self):
|
jpayne@69
|
78 raise NotImplementedError
|
jpayne@69
|
79
|
jpayne@69
|
80 def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
|
jpayne@69
|
81 *, daemon=None):
|
jpayne@69
|
82 assert group is None, 'group argument must be None for now'
|
jpayne@69
|
83 count = next(_process_counter)
|
jpayne@69
|
84 self._identity = _current_process._identity + (count,)
|
jpayne@69
|
85 self._config = _current_process._config.copy()
|
jpayne@69
|
86 self._parent_pid = os.getpid()
|
jpayne@69
|
87 self._parent_name = _current_process.name
|
jpayne@69
|
88 self._popen = None
|
jpayne@69
|
89 self._closed = False
|
jpayne@69
|
90 self._target = target
|
jpayne@69
|
91 self._args = tuple(args)
|
jpayne@69
|
92 self._kwargs = dict(kwargs)
|
jpayne@69
|
93 self._name = name or type(self).__name__ + '-' + \
|
jpayne@69
|
94 ':'.join(str(i) for i in self._identity)
|
jpayne@69
|
95 if daemon is not None:
|
jpayne@69
|
96 self.daemon = daemon
|
jpayne@69
|
97 _dangling.add(self)
|
jpayne@69
|
98
|
jpayne@69
|
99 def _check_closed(self):
|
jpayne@69
|
100 if self._closed:
|
jpayne@69
|
101 raise ValueError("process object is closed")
|
jpayne@69
|
102
|
jpayne@69
|
103 def run(self):
|
jpayne@69
|
104 '''
|
jpayne@69
|
105 Method to be run in sub-process; can be overridden in sub-class
|
jpayne@69
|
106 '''
|
jpayne@69
|
107 if self._target:
|
jpayne@69
|
108 self._target(*self._args, **self._kwargs)
|
jpayne@69
|
109
|
jpayne@69
|
110 def start(self):
|
jpayne@69
|
111 '''
|
jpayne@69
|
112 Start child process
|
jpayne@69
|
113 '''
|
jpayne@69
|
114 self._check_closed()
|
jpayne@69
|
115 assert self._popen is None, 'cannot start a process twice'
|
jpayne@69
|
116 assert self._parent_pid == os.getpid(), \
|
jpayne@69
|
117 'can only start a process object created by current process'
|
jpayne@69
|
118 assert not _current_process._config.get('daemon'), \
|
jpayne@69
|
119 'daemonic processes are not allowed to have children'
|
jpayne@69
|
120 _cleanup()
|
jpayne@69
|
121 self._popen = self._Popen(self)
|
jpayne@69
|
122 self._sentinel = self._popen.sentinel
|
jpayne@69
|
123 # Avoid a refcycle if the target function holds an indirect
|
jpayne@69
|
124 # reference to the process object (see bpo-30775)
|
jpayne@69
|
125 del self._target, self._args, self._kwargs
|
jpayne@69
|
126 _children.add(self)
|
jpayne@69
|
127
|
jpayne@69
|
128 def terminate(self):
|
jpayne@69
|
129 '''
|
jpayne@69
|
130 Terminate process; sends SIGTERM signal or uses TerminateProcess()
|
jpayne@69
|
131 '''
|
jpayne@69
|
132 self._check_closed()
|
jpayne@69
|
133 self._popen.terminate()
|
jpayne@69
|
134
|
jpayne@69
|
135 def kill(self):
|
jpayne@69
|
136 '''
|
jpayne@69
|
137 Terminate process; sends SIGKILL signal or uses TerminateProcess()
|
jpayne@69
|
138 '''
|
jpayne@69
|
139 self._check_closed()
|
jpayne@69
|
140 self._popen.kill()
|
jpayne@69
|
141
|
jpayne@69
|
142 def join(self, timeout=None):
|
jpayne@69
|
143 '''
|
jpayne@69
|
144 Wait until child process terminates
|
jpayne@69
|
145 '''
|
jpayne@69
|
146 self._check_closed()
|
jpayne@69
|
147 assert self._parent_pid == os.getpid(), 'can only join a child process'
|
jpayne@69
|
148 assert self._popen is not None, 'can only join a started process'
|
jpayne@69
|
149 res = self._popen.wait(timeout)
|
jpayne@69
|
150 if res is not None:
|
jpayne@69
|
151 _children.discard(self)
|
jpayne@69
|
152
|
jpayne@69
|
153 def is_alive(self):
|
jpayne@69
|
154 '''
|
jpayne@69
|
155 Return whether process is alive
|
jpayne@69
|
156 '''
|
jpayne@69
|
157 self._check_closed()
|
jpayne@69
|
158 if self is _current_process:
|
jpayne@69
|
159 return True
|
jpayne@69
|
160 assert self._parent_pid == os.getpid(), 'can only test a child process'
|
jpayne@69
|
161
|
jpayne@69
|
162 if self._popen is None:
|
jpayne@69
|
163 return False
|
jpayne@69
|
164
|
jpayne@69
|
165 returncode = self._popen.poll()
|
jpayne@69
|
166 if returncode is None:
|
jpayne@69
|
167 return True
|
jpayne@69
|
168 else:
|
jpayne@69
|
169 _children.discard(self)
|
jpayne@69
|
170 return False
|
jpayne@69
|
171
|
jpayne@69
|
172 def close(self):
|
jpayne@69
|
173 '''
|
jpayne@69
|
174 Close the Process object.
|
jpayne@69
|
175
|
jpayne@69
|
176 This method releases resources held by the Process object. It is
|
jpayne@69
|
177 an error to call this method if the child process is still running.
|
jpayne@69
|
178 '''
|
jpayne@69
|
179 if self._popen is not None:
|
jpayne@69
|
180 if self._popen.poll() is None:
|
jpayne@69
|
181 raise ValueError("Cannot close a process while it is still running. "
|
jpayne@69
|
182 "You should first call join() or terminate().")
|
jpayne@69
|
183 self._popen.close()
|
jpayne@69
|
184 self._popen = None
|
jpayne@69
|
185 del self._sentinel
|
jpayne@69
|
186 _children.discard(self)
|
jpayne@69
|
187 self._closed = True
|
jpayne@69
|
188
|
jpayne@69
|
189 @property
|
jpayne@69
|
190 def name(self):
|
jpayne@69
|
191 return self._name
|
jpayne@69
|
192
|
jpayne@69
|
193 @name.setter
|
jpayne@69
|
194 def name(self, name):
|
jpayne@69
|
195 assert isinstance(name, str), 'name must be a string'
|
jpayne@69
|
196 self._name = name
|
jpayne@69
|
197
|
jpayne@69
|
198 @property
|
jpayne@69
|
199 def daemon(self):
|
jpayne@69
|
200 '''
|
jpayne@69
|
201 Return whether process is a daemon
|
jpayne@69
|
202 '''
|
jpayne@69
|
203 return self._config.get('daemon', False)
|
jpayne@69
|
204
|
jpayne@69
|
205 @daemon.setter
|
jpayne@69
|
206 def daemon(self, daemonic):
|
jpayne@69
|
207 '''
|
jpayne@69
|
208 Set whether process is a daemon
|
jpayne@69
|
209 '''
|
jpayne@69
|
210 assert self._popen is None, 'process has already started'
|
jpayne@69
|
211 self._config['daemon'] = daemonic
|
jpayne@69
|
212
|
jpayne@69
|
213 @property
|
jpayne@69
|
214 def authkey(self):
|
jpayne@69
|
215 return self._config['authkey']
|
jpayne@69
|
216
|
jpayne@69
|
217 @authkey.setter
|
jpayne@69
|
218 def authkey(self, authkey):
|
jpayne@69
|
219 '''
|
jpayne@69
|
220 Set authorization key of process
|
jpayne@69
|
221 '''
|
jpayne@69
|
222 self._config['authkey'] = AuthenticationString(authkey)
|
jpayne@69
|
223
|
jpayne@69
|
224 @property
|
jpayne@69
|
225 def exitcode(self):
|
jpayne@69
|
226 '''
|
jpayne@69
|
227 Return exit code of process or `None` if it has yet to stop
|
jpayne@69
|
228 '''
|
jpayne@69
|
229 self._check_closed()
|
jpayne@69
|
230 if self._popen is None:
|
jpayne@69
|
231 return self._popen
|
jpayne@69
|
232 return self._popen.poll()
|
jpayne@69
|
233
|
jpayne@69
|
234 @property
|
jpayne@69
|
235 def ident(self):
|
jpayne@69
|
236 '''
|
jpayne@69
|
237 Return identifier (PID) of process or `None` if it has yet to start
|
jpayne@69
|
238 '''
|
jpayne@69
|
239 self._check_closed()
|
jpayne@69
|
240 if self is _current_process:
|
jpayne@69
|
241 return os.getpid()
|
jpayne@69
|
242 else:
|
jpayne@69
|
243 return self._popen and self._popen.pid
|
jpayne@69
|
244
|
jpayne@69
|
245 pid = ident
|
jpayne@69
|
246
|
jpayne@69
|
247 @property
|
jpayne@69
|
248 def sentinel(self):
|
jpayne@69
|
249 '''
|
jpayne@69
|
250 Return a file descriptor (Unix) or handle (Windows) suitable for
|
jpayne@69
|
251 waiting for process termination.
|
jpayne@69
|
252 '''
|
jpayne@69
|
253 self._check_closed()
|
jpayne@69
|
254 try:
|
jpayne@69
|
255 return self._sentinel
|
jpayne@69
|
256 except AttributeError:
|
jpayne@69
|
257 raise ValueError("process not started") from None
|
jpayne@69
|
258
|
jpayne@69
|
259 def __repr__(self):
|
jpayne@69
|
260 exitcode = None
|
jpayne@69
|
261 if self is _current_process:
|
jpayne@69
|
262 status = 'started'
|
jpayne@69
|
263 elif self._closed:
|
jpayne@69
|
264 status = 'closed'
|
jpayne@69
|
265 elif self._parent_pid != os.getpid():
|
jpayne@69
|
266 status = 'unknown'
|
jpayne@69
|
267 elif self._popen is None:
|
jpayne@69
|
268 status = 'initial'
|
jpayne@69
|
269 else:
|
jpayne@69
|
270 exitcode = self._popen.poll()
|
jpayne@69
|
271 if exitcode is not None:
|
jpayne@69
|
272 status = 'stopped'
|
jpayne@69
|
273 else:
|
jpayne@69
|
274 status = 'started'
|
jpayne@69
|
275
|
jpayne@69
|
276 info = [type(self).__name__, 'name=%r' % self._name]
|
jpayne@69
|
277 if self._popen is not None:
|
jpayne@69
|
278 info.append('pid=%s' % self._popen.pid)
|
jpayne@69
|
279 info.append('parent=%s' % self._parent_pid)
|
jpayne@69
|
280 info.append(status)
|
jpayne@69
|
281 if exitcode is not None:
|
jpayne@69
|
282 exitcode = _exitcode_to_name.get(exitcode, exitcode)
|
jpayne@69
|
283 info.append('exitcode=%s' % exitcode)
|
jpayne@69
|
284 if self.daemon:
|
jpayne@69
|
285 info.append('daemon')
|
jpayne@69
|
286 return '<%s>' % ' '.join(info)
|
jpayne@69
|
287
|
jpayne@69
|
288 ##
|
jpayne@69
|
289
|
jpayne@69
|
290 def _bootstrap(self, parent_sentinel=None):
|
jpayne@69
|
291 from . import util, context
|
jpayne@69
|
292 global _current_process, _parent_process, _process_counter, _children
|
jpayne@69
|
293
|
jpayne@69
|
294 try:
|
jpayne@69
|
295 if self._start_method is not None:
|
jpayne@69
|
296 context._force_start_method(self._start_method)
|
jpayne@69
|
297 _process_counter = itertools.count(1)
|
jpayne@69
|
298 _children = set()
|
jpayne@69
|
299 util._close_stdin()
|
jpayne@69
|
300 old_process = _current_process
|
jpayne@69
|
301 _current_process = self
|
jpayne@69
|
302 _parent_process = _ParentProcess(
|
jpayne@69
|
303 self._parent_name, self._parent_pid, parent_sentinel)
|
jpayne@69
|
304 if threading._HAVE_THREAD_NATIVE_ID:
|
jpayne@69
|
305 threading.main_thread()._set_native_id()
|
jpayne@69
|
306 try:
|
jpayne@69
|
307 util._finalizer_registry.clear()
|
jpayne@69
|
308 util._run_after_forkers()
|
jpayne@69
|
309 finally:
|
jpayne@69
|
310 # delay finalization of the old process object until after
|
jpayne@69
|
311 # _run_after_forkers() is executed
|
jpayne@69
|
312 del old_process
|
jpayne@69
|
313 util.info('child process calling self.run()')
|
jpayne@69
|
314 try:
|
jpayne@69
|
315 self.run()
|
jpayne@69
|
316 exitcode = 0
|
jpayne@69
|
317 finally:
|
jpayne@69
|
318 util._exit_function()
|
jpayne@69
|
319 except SystemExit as e:
|
jpayne@69
|
320 if not e.args:
|
jpayne@69
|
321 exitcode = 1
|
jpayne@69
|
322 elif isinstance(e.args[0], int):
|
jpayne@69
|
323 exitcode = e.args[0]
|
jpayne@69
|
324 else:
|
jpayne@69
|
325 sys.stderr.write(str(e.args[0]) + '\n')
|
jpayne@69
|
326 exitcode = 1
|
jpayne@69
|
327 except:
|
jpayne@69
|
328 exitcode = 1
|
jpayne@69
|
329 import traceback
|
jpayne@69
|
330 sys.stderr.write('Process %s:\n' % self.name)
|
jpayne@69
|
331 traceback.print_exc()
|
jpayne@69
|
332 finally:
|
jpayne@69
|
333 threading._shutdown()
|
jpayne@69
|
334 util.info('process exiting with exitcode %d' % exitcode)
|
jpayne@69
|
335 util._flush_std_streams()
|
jpayne@69
|
336
|
jpayne@69
|
337 return exitcode
|
jpayne@69
|
338
|
jpayne@69
|
339 #
|
jpayne@69
|
340 # We subclass bytes to avoid accidental transmission of auth keys over network
|
jpayne@69
|
341 #
|
jpayne@69
|
342
|
jpayne@69
|
343 class AuthenticationString(bytes):
|
jpayne@69
|
344 def __reduce__(self):
|
jpayne@69
|
345 from .context import get_spawning_popen
|
jpayne@69
|
346 if get_spawning_popen() is None:
|
jpayne@69
|
347 raise TypeError(
|
jpayne@69
|
348 'Pickling an AuthenticationString object is '
|
jpayne@69
|
349 'disallowed for security reasons'
|
jpayne@69
|
350 )
|
jpayne@69
|
351 return AuthenticationString, (bytes(self),)
|
jpayne@69
|
352
|
jpayne@69
|
353
|
jpayne@69
|
354 #
|
jpayne@69
|
355 # Create object representing the parent process
|
jpayne@69
|
356 #
|
jpayne@69
|
357
|
jpayne@69
|
358 class _ParentProcess(BaseProcess):
|
jpayne@69
|
359
|
jpayne@69
|
360 def __init__(self, name, pid, sentinel):
|
jpayne@69
|
361 self._identity = ()
|
jpayne@69
|
362 self._name = name
|
jpayne@69
|
363 self._pid = pid
|
jpayne@69
|
364 self._parent_pid = None
|
jpayne@69
|
365 self._popen = None
|
jpayne@69
|
366 self._closed = False
|
jpayne@69
|
367 self._sentinel = sentinel
|
jpayne@69
|
368 self._config = {}
|
jpayne@69
|
369
|
jpayne@69
|
370 def is_alive(self):
|
jpayne@69
|
371 from multiprocessing.connection import wait
|
jpayne@69
|
372 return not wait([self._sentinel], timeout=0)
|
jpayne@69
|
373
|
jpayne@69
|
374 @property
|
jpayne@69
|
375 def ident(self):
|
jpayne@69
|
376 return self._pid
|
jpayne@69
|
377
|
jpayne@69
|
378 def join(self, timeout=None):
|
jpayne@69
|
379 '''
|
jpayne@69
|
380 Wait until parent process terminates
|
jpayne@69
|
381 '''
|
jpayne@69
|
382 from multiprocessing.connection import wait
|
jpayne@69
|
383 wait([self._sentinel], timeout=timeout)
|
jpayne@69
|
384
|
jpayne@69
|
385 pid = ident
|
jpayne@69
|
386
|
jpayne@69
|
387 #
|
jpayne@69
|
388 # Create object representing the main process
|
jpayne@69
|
389 #
|
jpayne@69
|
390
|
jpayne@69
|
391 class _MainProcess(BaseProcess):
|
jpayne@69
|
392
|
jpayne@69
|
393 def __init__(self):
|
jpayne@69
|
394 self._identity = ()
|
jpayne@69
|
395 self._name = 'MainProcess'
|
jpayne@69
|
396 self._parent_pid = None
|
jpayne@69
|
397 self._popen = None
|
jpayne@69
|
398 self._closed = False
|
jpayne@69
|
399 self._config = {'authkey': AuthenticationString(os.urandom(32)),
|
jpayne@69
|
400 'semprefix': '/mp'}
|
jpayne@69
|
401 # Note that some versions of FreeBSD only allow named
|
jpayne@69
|
402 # semaphores to have names of up to 14 characters. Therefore
|
jpayne@69
|
403 # we choose a short prefix.
|
jpayne@69
|
404 #
|
jpayne@69
|
405 # On MacOSX in a sandbox it may be necessary to use a
|
jpayne@69
|
406 # different prefix -- see #19478.
|
jpayne@69
|
407 #
|
jpayne@69
|
408 # Everything in self._config will be inherited by descendant
|
jpayne@69
|
409 # processes.
|
jpayne@69
|
410
|
jpayne@69
|
411 def close(self):
|
jpayne@69
|
412 pass
|
jpayne@69
|
413
|
jpayne@69
|
414
|
jpayne@69
|
415 _parent_process = None
|
jpayne@69
|
416 _current_process = _MainProcess()
|
jpayne@69
|
417 _process_counter = itertools.count(1)
|
jpayne@69
|
418 _children = set()
|
jpayne@69
|
419 del _MainProcess
|
jpayne@69
|
420
|
jpayne@69
|
421 #
|
jpayne@69
|
422 # Give names to some return codes
|
jpayne@69
|
423 #
|
jpayne@69
|
424
|
jpayne@69
|
425 _exitcode_to_name = {}
|
jpayne@69
|
426
|
jpayne@69
|
427 for name, signum in list(signal.__dict__.items()):
|
jpayne@69
|
428 if name[:3]=='SIG' and '_' not in name:
|
jpayne@69
|
429 _exitcode_to_name[-signum] = f'-{name}'
|
jpayne@69
|
430
|
jpayne@69
|
431 # For debug and leak testing
|
jpayne@69
|
432 _dangling = WeakSet()
|