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 17import os 18import sys 19import signal 20import itertools 21import threading 22from _weakrefset import WeakSet 23 24# 25# 26# 27 28try: 29 ORIGINAL_DIR = os.path.abspath(os.getcwd()) 30except OSError: 31 ORIGINAL_DIR = None 32 33# 34# Public functions 35# 36 37def current_process(): 38 ''' 39 Return process object representing the current process 40 ''' 41 return _current_process 42 43def active_children(): 44 ''' 45 Return list of process objects corresponding to live child processes 46 ''' 47 _cleanup() 48 return list(_children) 49 50 51def parent_process(): 52 ''' 53 Return process object representing the parent process 54 ''' 55 return _parent_process 56 57# 58# 59# 60 61def _cleanup(): 62 # check for processes which have finished 63 for p in list(_children): 64 if (child_popen := p._popen) and child_popen.poll() is not None: 65 _children.discard(p) 66 67# 68# The `Process` class 69# 70 71class 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 self._after_fork() 308 finally: 309 # delay finalization of the old process object until after 310 # _run_after_forkers() is executed 311 del old_process 312 util.info('child process calling self.run()') 313 try: 314 self.run() 315 exitcode = 0 316 finally: 317 util._exit_function() 318 except SystemExit as e: 319 if e.code is None: 320 exitcode = 0 321 elif isinstance(e.code, int): 322 exitcode = e.code 323 else: 324 sys.stderr.write(str(e.code) + '\n') 325 exitcode = 1 326 except: 327 exitcode = 1 328 import traceback 329 sys.stderr.write('Process %s:\n' % self.name) 330 traceback.print_exc() 331 finally: 332 threading._shutdown() 333 util.info('process exiting with exitcode %d' % exitcode) 334 util._flush_std_streams() 335 336 return exitcode 337 338 @staticmethod 339 def _after_fork(): 340 from . import util 341 util._finalizer_registry.clear() 342 util._run_after_forkers() 343 344 345# 346# We subclass bytes to avoid accidental transmission of auth keys over network 347# 348 349class AuthenticationString(bytes): 350 def __reduce__(self): 351 from .context import get_spawning_popen 352 if get_spawning_popen() is None: 353 raise TypeError( 354 'Pickling an AuthenticationString object is ' 355 'disallowed for security reasons' 356 ) 357 return AuthenticationString, (bytes(self),) 358 359 360# 361# Create object representing the parent process 362# 363 364class _ParentProcess(BaseProcess): 365 366 def __init__(self, name, pid, sentinel): 367 self._identity = () 368 self._name = name 369 self._pid = pid 370 self._parent_pid = None 371 self._popen = None 372 self._closed = False 373 self._sentinel = sentinel 374 self._config = {} 375 376 def is_alive(self): 377 from multiprocessing.connection import wait 378 return not wait([self._sentinel], timeout=0) 379 380 @property 381 def ident(self): 382 return self._pid 383 384 def join(self, timeout=None): 385 ''' 386 Wait until parent process terminates 387 ''' 388 from multiprocessing.connection import wait 389 wait([self._sentinel], timeout=timeout) 390 391 pid = ident 392 393# 394# Create object representing the main process 395# 396 397class _MainProcess(BaseProcess): 398 399 def __init__(self): 400 self._identity = () 401 self._name = 'MainProcess' 402 self._parent_pid = None 403 self._popen = None 404 self._closed = False 405 self._config = {'authkey': AuthenticationString(os.urandom(32)), 406 'semprefix': '/mp'} 407 # Note that some versions of FreeBSD only allow named 408 # semaphores to have names of up to 14 characters. Therefore 409 # we choose a short prefix. 410 # 411 # On MacOSX in a sandbox it may be necessary to use a 412 # different prefix -- see #19478. 413 # 414 # Everything in self._config will be inherited by descendant 415 # processes. 416 417 def close(self): 418 pass 419 420 421_parent_process = None 422_current_process = _MainProcess() 423_process_counter = itertools.count(1) 424_children = set() 425del _MainProcess 426 427# 428# Give names to some return codes 429# 430 431_exitcode_to_name = {} 432 433for name, signum in list(signal.__dict__.items()): 434 if name[:3]=='SIG' and '_' not in name: 435 _exitcode_to_name[-signum] = f'-{name}' 436del name, signum 437 438# For debug and leak testing 439_dangling = WeakSet() 440