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 p._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 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 e.code is None: 321 exitcode = 0 322 elif isinstance(e.code, int): 323 exitcode = e.code 324 else: 325 sys.stderr.write(str(e.code) + '\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 343class 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 358class _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 391class _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() 419del _MainProcess 420 421# 422# Give names to some return codes 423# 424 425_exitcode_to_name = {} 426 427for 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() 433