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 12 # 13 # Imports 14 # 15 16 import os 17 import sys 18 import signal 19 import itertools 20 from _weakrefset import WeakSet 21 22 # 23 # 24 # 25 26 try: 27 ORIGINAL_DIR = os.path.abspath(os.getcwd()) 28 except OSError: 29 ORIGINAL_DIR = None 30 31 # 32 # Public functions 33 # 34 35 def current_process(): 36 ''' 37 Return process object representing the current process 38 ''' 39 return _current_process 40 41 def active_children(): 42 ''' 43 Return list of process objects corresponding to live child processes 44 ''' 45 _cleanup() 46 return list(_children) 47 48 # 49 # 50 # 51 52 def _cleanup(): 53 # check for processes which have finished 54 for p in list(_children): 55 if p._popen.poll() is not None: 56 _children.discard(p) 57 58 # 59 # The `Process` class 60 # 61 62 class BaseProcess(object): 63 ''' 64 Process objects represent activity that is run in a separate process 65 66 The class is analogous to `threading.Thread` 67 ''' 68 def _Popen(self): 69 raise NotImplementedError 70 71 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, 72 *, daemon=None): 73 assert group is None, 'group argument must be None for now' 74 count = next(_process_counter) 75 self._identity = _current_process._identity + (count,) 76 self._config = _current_process._config.copy() 77 self._parent_pid = os.getpid() 78 self._popen = None 79 self._target = target 80 self._args = tuple(args) 81 self._kwargs = dict(kwargs) 82 self._name = name or type(self).__name__ + '-' + \ 83 ':'.join(str(i) for i in self._identity) 84 if daemon is not None: 85 self.daemon = daemon 86 _dangling.add(self) 87 88 def run(self): 89 ''' 90 Method to be run in sub-process; can be overridden in sub-class 91 ''' 92 if self._target: 93 self._target(*self._args, **self._kwargs) 94 95 def start(self): 96 ''' 97 Start child process 98 ''' 99 assert self._popen is None, 'cannot start a process twice' 100 assert self._parent_pid == os.getpid(), \ 101 'can only start a process object created by current process' 102 assert not _current_process._config.get('daemon'), \ 103 'daemonic processes are not allowed to have children' 104 _cleanup() 105 self._popen = self._Popen(self) 106 self._sentinel = self._popen.sentinel 107 _children.add(self) 108 109 def terminate(self): 110 ''' 111 Terminate process; sends SIGTERM signal or uses TerminateProcess() 112 ''' 113 self._popen.terminate() 114 115 def join(self, timeout=None): 116 ''' 117 Wait until child process terminates 118 ''' 119 assert self._parent_pid == os.getpid(), 'can only join a child process' 120 assert self._popen is not None, 'can only join a started process' 121 res = self._popen.wait(timeout) 122 if res is not None: 123 _children.discard(self) 124 125 def is_alive(self): 126 ''' 127 Return whether process is alive 128 ''' 129 if self is _current_process: 130 return True 131 assert self._parent_pid == os.getpid(), 'can only test a child process' 132 if self._popen is None: 133 return False 134 self._popen.poll() 135 return self._popen.returncode is None 136 137 @property 138 def name(self): 139 return self._name 140 141 @name.setter 142 def name(self, name): 143 assert isinstance(name, str), 'name must be a string' 144 self._name = name 145 146 @property 147 def daemon(self): 148 ''' 149 Return whether process is a daemon 150 ''' 151 return self._config.get('daemon', False) 152 153 @daemon.setter 154 def daemon(self, daemonic): 155 ''' 156 Set whether process is a daemon 157 ''' 158 assert self._popen is None, 'process has already started' 159 self._config['daemon'] = daemonic 160 161 @property 162 def authkey(self): 163 return self._config['authkey'] 164 165 @authkey.setter 166 def authkey(self, authkey): 167 ''' 168 Set authorization key of process 169 ''' 170 self._config['authkey'] = AuthenticationString(authkey) 171 172 @property 173 def exitcode(self): 174 ''' 175 Return exit code of process or `None` if it has yet to stop 176 ''' 177 if self._popen is None: 178 return self._popen 179 return self._popen.poll() 180 181 @property 182 def ident(self): 183 ''' 184 Return identifier (PID) of process or `None` if it has yet to start 185 ''' 186 if self is _current_process: 187 return os.getpid() 188 else: 189 return self._popen and self._popen.pid 190 191 pid = ident 192 193 @property 194 def sentinel(self): 195 ''' 196 Return a file descriptor (Unix) or handle (Windows) suitable for 197 waiting for process termination. 198 ''' 199 try: 200 return self._sentinel 201 except AttributeError: 202 raise ValueError("process not started") 203 204 def __repr__(self): 205 if self is _current_process: 206 status = 'started' 207 elif self._parent_pid != os.getpid(): 208 status = 'unknown' 209 elif self._popen is None: 210 status = 'initial' 211 else: 212 if self._popen.poll() is not None: 213 status = self.exitcode 214 else: 215 status = 'started' 216 217 if type(status) is int: 218 if status == 0: 219 status = 'stopped' 220 else: 221 status = 'stopped[%s]' % _exitcode_to_name.get(status, status) 222 223 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name, 224 status, self.daemon and ' daemon' or '') 225 226 ## 227 228 def _bootstrap(self): 229 from . import util, context 230 global _current_process, _process_counter, _children 231 232 try: 233 if self._start_method is not None: 234 context._force_start_method(self._start_method) 235 _process_counter = itertools.count(1) 236 _children = set() 237 util._close_stdin() 238 old_process = _current_process 239 _current_process = self 240 try: 241 util._finalizer_registry.clear() 242 util._run_after_forkers() 243 finally: 244 # delay finalization of the old process object until after 245 # _run_after_forkers() is executed 246 del old_process 247 util.info('child process calling self.run()') 248 try: 249 self.run() 250 exitcode = 0 251 finally: 252 util._exit_function() 253 except SystemExit as e: 254 if not e.args: 255 exitcode = 1 256 elif isinstance(e.args[0], int): 257 exitcode = e.args[0] 258 else: 259 sys.stderr.write(str(e.args[0]) + '\n') 260 exitcode = 1 261 except: 262 exitcode = 1 263 import traceback 264 sys.stderr.write('Process %s:\n' % self.name) 265 traceback.print_exc() 266 finally: 267 util.info('process exiting with exitcode %d' % exitcode) 268 sys.stdout.flush() 269 sys.stderr.flush() 270 271 return exitcode 272 273 # 274 # We subclass bytes to avoid accidental transmission of auth keys over network 275 # 276 277 class AuthenticationString(bytes): 278 def __reduce__(self): 279 from .context import get_spawning_popen 280 if get_spawning_popen() is None: 281 raise TypeError( 282 'Pickling an AuthenticationString object is ' 283 'disallowed for security reasons' 284 ) 285 return AuthenticationString, (bytes(self),) 286 287 # 288 # Create object representing the main process 289 # 290 291 class _MainProcess(BaseProcess): 292 293 def __init__(self): 294 self._identity = () 295 self._name = 'MainProcess' 296 self._parent_pid = None 297 self._popen = None 298 self._config = {'authkey': AuthenticationString(os.urandom(32)), 299 'semprefix': '/mp'} 300 # Note that some versions of FreeBSD only allow named 301 # semaphores to have names of up to 14 characters. Therefore 302 # we choose a short prefix. 303 # 304 # On MacOSX in a sandbox it may be necessary to use a 305 # different prefix -- see #19478. 306 # 307 # Everything in self._config will be inherited by descendant 308 # processes. 309 310 311 _current_process = _MainProcess() 312 _process_counter = itertools.count(1) 313 _children = set() 314 del _MainProcess 315 316 # 317 # Give names to some return codes 318 # 319 320 _exitcode_to_name = {} 321 322 for name, signum in list(signal.__dict__.items()): 323 if name[:3]=='SIG' and '_' not in name: 324 _exitcode_to_name[-signum] = name 325 326 # For debug and leak testing 327 _dangling = WeakSet() 328