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 41import os 42import sys 43import signal 44import itertools 45 46# 47# 48# 49 50try: 51 ORIGINAL_DIR = os.path.abspath(os.getcwd()) 52except OSError: 53 ORIGINAL_DIR = None 54 55# 56# Public functions 57# 58 59def current_process(): 60 ''' 61 Return process object representing the current process 62 ''' 63 return _current_process 64 65def 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 76def _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 86class 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 # Avoid a refcycle if the target function holds an indirect 132 # reference to the process object (see bpo-30775) 133 del self._target, self._args, self._kwargs 134 _current_process._children.add(self) 135 136 def terminate(self): 137 ''' 138 Terminate process; sends SIGTERM signal or uses TerminateProcess() 139 ''' 140 self._popen.terminate() 141 142 def join(self, timeout=None): 143 ''' 144 Wait until child process terminates 145 ''' 146 assert self._parent_pid == os.getpid(), 'can only join a child process' 147 assert self._popen is not None, 'can only join a started process' 148 res = self._popen.wait(timeout) 149 if res is not None: 150 _current_process._children.discard(self) 151 152 def is_alive(self): 153 ''' 154 Return whether process is alive 155 ''' 156 if self is _current_process: 157 return True 158 assert self._parent_pid == os.getpid(), 'can only test a child process' 159 160 if self._popen is None: 161 return False 162 163 returncode = self._popen.poll() 164 if returncode is None: 165 return True 166 else: 167 _current_process._children.discard(self) 168 return False 169 170 @property 171 def name(self): 172 return self._name 173 174 @name.setter 175 def name(self, name): 176 assert isinstance(name, basestring), 'name must be a string' 177 self._name = name 178 179 @property 180 def daemon(self): 181 ''' 182 Return whether process is a daemon 183 ''' 184 return self._daemonic 185 186 @daemon.setter 187 def daemon(self, daemonic): 188 ''' 189 Set whether process is a daemon 190 ''' 191 assert self._popen is None, 'process has already started' 192 self._daemonic = daemonic 193 194 @property 195 def authkey(self): 196 return self._authkey 197 198 @authkey.setter 199 def authkey(self, authkey): 200 ''' 201 Set authorization key of process 202 ''' 203 self._authkey = AuthenticationString(authkey) 204 205 @property 206 def exitcode(self): 207 ''' 208 Return exit code of process or `None` if it has yet to stop 209 ''' 210 if self._popen is None: 211 return self._popen 212 return self._popen.poll() 213 214 @property 215 def ident(self): 216 ''' 217 Return identifier (PID) of process or `None` if it has yet to start 218 ''' 219 if self is _current_process: 220 return os.getpid() 221 else: 222 return self._popen and self._popen.pid 223 224 pid = ident 225 226 def __repr__(self): 227 if self is _current_process: 228 status = 'started' 229 elif self._parent_pid != os.getpid(): 230 status = 'unknown' 231 elif self._popen is None: 232 status = 'initial' 233 else: 234 if self._popen.poll() is not None: 235 status = self.exitcode 236 else: 237 status = 'started' 238 239 if type(status) in (int, long): 240 if status == 0: 241 status = 'stopped' 242 else: 243 status = 'stopped[%s]' % _exitcode_to_name.get(status, status) 244 245 return '<%s(%s, %s%s)>' % (type(self).__name__, self._name, 246 status, self._daemonic and ' daemon' or '') 247 248 ## 249 250 def _bootstrap(self): 251 from . import util 252 global _current_process 253 254 try: 255 self._children = set() 256 self._counter = itertools.count(1) 257 try: 258 sys.stdin.close() 259 sys.stdin = open(os.devnull) 260 except (OSError, ValueError): 261 pass 262 _current_process = self 263 util._finalizer_registry.clear() 264 util._run_after_forkers() 265 util.info('child process calling self.run()') 266 try: 267 self.run() 268 exitcode = 0 269 finally: 270 util._exit_function() 271 except SystemExit, e: 272 if not e.args: 273 exitcode = 1 274 elif isinstance(e.args[0], (int, long)): 275 exitcode = int(e.args[0]) 276 else: 277 sys.stderr.write(str(e.args[0]) + '\n') 278 sys.stderr.flush() 279 exitcode = 1 280 except: 281 exitcode = 1 282 import traceback 283 sys.stderr.write('Process %s:\n' % self.name) 284 sys.stderr.flush() 285 traceback.print_exc() 286 287 util.info('process exiting with exitcode %d' % exitcode) 288 return exitcode 289 290# 291# We subclass bytes to avoid accidental transmission of auth keys over network 292# 293 294class AuthenticationString(bytes): 295 def __reduce__(self): 296 from .forking import Popen 297 if not Popen.thread_is_spawning(): 298 raise TypeError( 299 'Pickling an AuthenticationString object is ' 300 'disallowed for security reasons' 301 ) 302 return AuthenticationString, (bytes(self),) 303 304# 305# Create object representing the main process 306# 307 308class _MainProcess(Process): 309 310 def __init__(self): 311 self._identity = () 312 self._daemonic = False 313 self._name = 'MainProcess' 314 self._parent_pid = None 315 self._popen = None 316 self._counter = itertools.count(1) 317 self._children = set() 318 self._authkey = AuthenticationString(os.urandom(32)) 319 self._tempdir = None 320 321_current_process = _MainProcess() 322del _MainProcess 323 324# 325# Give names to some return codes 326# 327 328_exitcode_to_name = {} 329 330for name, signum in signal.__dict__.items(): 331 if name[:3]=='SIG' and '_' not in name: 332 _exitcode_to_name[-signum] = name 333