comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/multiprocessing/process.py @ 69:33d812a61356

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