Mercurial > repos > rliterman > csp2
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() |