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 # All rights reserved. 8 # 9 # Redistribution and use in source and binary forms, with or without 10 # modification, are permitted provided that the following conditions 11 # are met: 12 # 13 # 1. Redistributions of source code must retain the above copyright 14 # notice, this list of conditions and the following disclaimer. 15 # 2. Redistributions in binary form must reproduce the above copyright 16 # notice, this list of conditions and the following disclaimer in the 17 # documentation and/or other materials provided with the distribution. 18 # 3. Neither the name of author nor the names of any contributors may be 19 # used to endorse or promote products derived from this software 20 # without specific prior written permission. 21 # 22 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND 23 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 # SUCH DAMAGE. 33 # 34 35 __all__ = ['Process', 'current_process', 'active_children'] 36 37 # 38 # Imports 39 # 40 41 import os 42 import sys 43 import signal 44 import itertools 45 46 # 47 # 48 # 49 50 try: 51 ORIGINAL_DIR = os.path.abspath(os.getcwd()) 52 except OSError: 53 ORIGINAL_DIR = None 54 55 # 56 # Public functions 57 # 58 59 def current_process(): 60 ''' 61 Return process object representing the current process 62 ''' 63 return _current_process 64 65 def active_children(): 66 ''' 67 Return list of process objects corresponding to live child processes 68 ''' 69 _cleanup() 70 return list(_current_process._children) 71 72 # 73 # 74 # 75 76 def _cleanup(): 77 # check for processes which have finished 78 for p in list(_current_process._children): 79 if p._popen.poll() is not None: 80 _current_process._children.discard(p) 81 82 # 83 # The `Process` class 84 # 85 86 class Process(object): 87 ''' 88 Process objects represent activity that is run in a separate process 89 90 The class is analagous to `threading.Thread` 91 ''' 92 _Popen = None 93 94 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): 95 assert group is None, 'group argument must be None for now' 96 count = _current_process._counter.next() 97 self._identity = _current_process._identity + (count,) 98 self._authkey = _current_process._authkey 99 self._daemonic = _current_process._daemonic 100 self._tempdir = _current_process._tempdir 101 self._parent_pid = os.getpid() 102 self._popen = None 103 self._target = target 104 self._args = tuple(args) 105 self._kwargs = dict(kwargs) 106 self._name = name or type(self).__name__ + '-' + \ 107 ':'.join(str(i) for i in self._identity) 108 109 def run(self): 110 ''' 111 Method to be run in sub-process; can be overridden in sub-class 112 ''' 113 if self._target: 114 self._target(*self._args, **self._kwargs) 115 116 def start(self): 117 ''' 118 Start child process 119 ''' 120 assert self._popen is None, 'cannot start a process twice' 121 assert self._parent_pid == os.getpid(), \ 122 'can only start a process object created by current process' 123 assert not _current_process._daemonic, \ 124 'daemonic processes are not allowed to have children' 125 _cleanup() 126 if self._Popen is not None: 127 Popen = self._Popen 128 else: 129 from .forking import Popen 130 self._popen = Popen(self) 131 _current_process._children.add(self) 132 133 def terminate(self): 134 ''' 135 Terminate process; sends SIGTERM signal or uses TerminateProcess() 136 ''' 137 self._popen.terminate() 138 139 def join(self, timeout=None): 140 ''' 141 Wait until child process terminates 142 ''' 143 assert self._parent_pid == os.getpid(), 'can only join a child process' 144 assert self._popen is not None, 'can only join a started process' 145 res = self._popen.wait(timeout) 146 if res is not None: 147 _current_process._children.discard(self) 148 149 def is_alive(self): 150 ''' 151 Return whether process is alive 152 ''' 153 if self is _current_process: 154 return True 155 assert self._parent_pid == os.getpid(), 'can only test a child process' 156 if self._popen is None: 157 return False 158 self._popen.poll() 159 return self._popen.returncode is None 160 161 @property 162 def name(self): 163 return self._name 164 165 @name.setter 166 def name(self, name): 167 assert isinstance(name, basestring), 'name must be a string' 168 self._name = name 169 170 @property 171 def daemon(self): 172 ''' 173 Return whether process is a daemon 174 ''' 175 return self._daemonic 176 177 @daemon.setter 178 def daemon(self, daemonic): 179 ''' 180 Set whether process is a daemon 181 ''' 182 assert self._popen is None, 'process has already started' 183 self._daemonic = daemonic 184 185 @property 186 def authkey(self): 187 return self._authkey 188 189 @authkey.setter 190 def authkey(self, authkey): 191 ''' 192 Set authorization key of process 193 ''' 194 self._authkey = AuthenticationString(authkey) 195 196 @property 197 def exitcode(self): 198 ''' 199 Return exit code of process or `None` if it has yet to stop 200 ''' 201 if self._popen is None: 202 return self._popen 203 return self._popen.poll() 204 205 @property 206 def ident(self): 207 ''' 208 Return identifier (PID) of process or `None` if it has yet to start 209 ''' 210 if self is _current_process: 211 return os.getpid() 212 else: 213 return self._popen and self._popen.pid 214 215 pid = ident 216 217 def __repr__(self): 218 if self is _current_process: 219 status = 'started' 220 elif self._parent_pid != os.getpid(): 221 status = 'unknown' 222 elif self._popen is None: 223 status = 'initial' 224 else: 225 if self._popen.poll() is not None: 226 status = self.exitcode 227 else: 228 status = 'started' 229 230 if type(status) is int: 231 if status == 0: 232 status = 'stopped' 233 else: 234 status = 'stopped[%s]' % _exitcode_to_name.get(status, status) 235 236 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name, 237 status, self._daemonic and ' daemon' or '') 238 239 ## 240 241 def _bootstrap(self): 242 from . import util 243 global _current_process 244 245 try: 246 self._children = set() 247 self._counter = itertools.count(1) 248 try: 249 sys.stdin.close() 250 sys.stdin = open(os.devnull) 251 except (OSError, ValueError): 252 pass 253 _current_process = self 254 util._finalizer_registry.clear() 255 util._run_after_forkers() 256 util.info('child process calling self.run()') 257 try: 258 self.run() 259 exitcode = 0 260 finally: 261 util._exit_function() 262 except SystemExit, e: 263 if not e.args: 264 exitcode = 1 265 elif isinstance(e.args[0], int): 266 exitcode = e.args[0] 267 else: 268 sys.stderr.write(str(e.args[0]) + '\n') 269 sys.stderr.flush() 270 exitcode = 0 if isinstance(e.args[0], str) else 1 271 except: 272 exitcode = 1 273 import traceback 274 sys.stderr.write('Process %s:\n' % self.name) 275 sys.stderr.flush() 276 traceback.print_exc() 277 278 util.info('process exiting with exitcode %d' % exitcode) 279 return exitcode 280 281 # 282 # We subclass bytes to avoid accidental transmission of auth keys over network 283 # 284 285 class AuthenticationString(bytes): 286 def __reduce__(self): 287 from .forking import Popen 288 if not Popen.thread_is_spawning(): 289 raise TypeError( 290 'Pickling an AuthenticationString object is ' 291 'disallowed for security reasons' 292 ) 293 return AuthenticationString, (bytes(self),) 294 295 # 296 # Create object representing the main process 297 # 298 299 class _MainProcess(Process): 300 301 def __init__(self): 302 self._identity = () 303 self._daemonic = False 304 self._name = 'MainProcess' 305 self._parent_pid = None 306 self._popen = None 307 self._counter = itertools.count(1) 308 self._children = set() 309 self._authkey = AuthenticationString(os.urandom(32)) 310 self._tempdir = None 311 312 _current_process = _MainProcess() 313 del _MainProcess 314 315 # 316 # Give names to some return codes 317 # 318 319 _exitcode_to_name = {} 320 321 for name, signum in signal.__dict__.items(): 322 if name[:3]=='SIG' and '_' not in name: 323 _exitcode_to_name[-signum] = name 324