Spaces:
Running
Running
# | |
# Module providing the `Process` class which emulates `threading.Thread` | |
# | |
# multiprocessing/process.py | |
# | |
# Copyright (c) 2006-2008, R Oudkerk | |
# Licensed to PSF under a Contributor Agreement. | |
# | |
__all__ = ['BaseProcess', 'current_process', 'active_children', | |
'parent_process'] | |
# | |
# Imports | |
# | |
import os | |
import sys | |
import signal | |
import itertools | |
import threading | |
from _weakrefset import WeakSet | |
# | |
# | |
# | |
try: | |
ORIGINAL_DIR = os.path.abspath(os.getcwd()) | |
except OSError: | |
ORIGINAL_DIR = None | |
# | |
# Public functions | |
# | |
def current_process(): | |
''' | |
Return process object representing the current process | |
''' | |
return _current_process | |
def active_children(): | |
''' | |
Return list of process objects corresponding to live child processes | |
''' | |
_cleanup() | |
return list(_children) | |
def parent_process(): | |
''' | |
Return process object representing the parent process | |
''' | |
return _parent_process | |
# | |
# | |
# | |
def _cleanup(): | |
# check for processes which have finished | |
for p in list(_children): | |
if p._popen.poll() is not None: | |
_children.discard(p) | |
# | |
# The `Process` class | |
# | |
class BaseProcess(object): | |
''' | |
Process objects represent activity that is run in a separate process | |
The class is analogous to `threading.Thread` | |
''' | |
def _Popen(self): | |
raise NotImplementedError | |
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, | |
*, daemon=None): | |
assert group is None, 'group argument must be None for now' | |
count = next(_process_counter) | |
self._identity = _current_process._identity + (count,) | |
self._config = _current_process._config.copy() | |
self._parent_pid = os.getpid() | |
self._parent_name = _current_process.name | |
self._popen = None | |
self._closed = False | |
self._target = target | |
self._args = tuple(args) | |
self._kwargs = dict(kwargs) | |
self._name = name or type(self).__name__ + '-' + \ | |
':'.join(str(i) for i in self._identity) | |
if daemon is not None: | |
self.daemon = daemon | |
_dangling.add(self) | |
def _check_closed(self): | |
if self._closed: | |
raise ValueError("process object is closed") | |
def run(self): | |
''' | |
Method to be run in sub-process; can be overridden in sub-class | |
''' | |
if self._target: | |
self._target(*self._args, **self._kwargs) | |
def start(self): | |
''' | |
Start child process | |
''' | |
self._check_closed() | |
assert self._popen is None, 'cannot start a process twice' | |
assert self._parent_pid == os.getpid(), \ | |
'can only start a process object created by current process' | |
assert not _current_process._config.get('daemon'), \ | |
'daemonic processes are not allowed to have children' | |
_cleanup() | |
self._popen = self._Popen(self) | |
self._sentinel = self._popen.sentinel | |
# Avoid a refcycle if the target function holds an indirect | |
# reference to the process object (see bpo-30775) | |
del self._target, self._args, self._kwargs | |
_children.add(self) | |
def terminate(self): | |
''' | |
Terminate process; sends SIGTERM signal or uses TerminateProcess() | |
''' | |
self._check_closed() | |
self._popen.terminate() | |
def kill(self): | |
''' | |
Terminate process; sends SIGKILL signal or uses TerminateProcess() | |
''' | |
self._check_closed() | |
self._popen.kill() | |
def join(self, timeout=None): | |
''' | |
Wait until child process terminates | |
''' | |
self._check_closed() | |
assert self._parent_pid == os.getpid(), 'can only join a child process' | |
assert self._popen is not None, 'can only join a started process' | |
res = self._popen.wait(timeout) | |
if res is not None: | |
_children.discard(self) | |
def is_alive(self): | |
''' | |
Return whether process is alive | |
''' | |
self._check_closed() | |
if self is _current_process: | |
return True | |
assert self._parent_pid == os.getpid(), 'can only test a child process' | |
if self._popen is None: | |
return False | |
returncode = self._popen.poll() | |
if returncode is None: | |
return True | |
else: | |
_children.discard(self) | |
return False | |
def close(self): | |
''' | |
Close the Process object. | |
This method releases resources held by the Process object. It is | |
an error to call this method if the child process is still running. | |
''' | |
if self._popen is not None: | |
if self._popen.poll() is None: | |
raise ValueError("Cannot close a process while it is still running. " | |
"You should first call join() or terminate().") | |
self._popen.close() | |
self._popen = None | |
del self._sentinel | |
_children.discard(self) | |
self._closed = True | |
def name(self): | |
return self._name | |
def name(self, name): | |
assert isinstance(name, str), 'name must be a string' | |
self._name = name | |
def daemon(self): | |
''' | |
Return whether process is a daemon | |
''' | |
return self._config.get('daemon', False) | |
def daemon(self, daemonic): | |
''' | |
Set whether process is a daemon | |
''' | |
assert self._popen is None, 'process has already started' | |
self._config['daemon'] = daemonic | |
def authkey(self): | |
return self._config['authkey'] | |
def authkey(self, authkey): | |
''' | |
Set authorization key of process | |
''' | |
self._config['authkey'] = AuthenticationString(authkey) | |
def exitcode(self): | |
''' | |
Return exit code of process or `None` if it has yet to stop | |
''' | |
self._check_closed() | |
if self._popen is None: | |
return self._popen | |
return self._popen.poll() | |
def ident(self): | |
''' | |
Return identifier (PID) of process or `None` if it has yet to start | |
''' | |
self._check_closed() | |
if self is _current_process: | |
return os.getpid() | |
else: | |
return self._popen and self._popen.pid | |
pid = ident | |
def sentinel(self): | |
''' | |
Return a file descriptor (Unix) or handle (Windows) suitable for | |
waiting for process termination. | |
''' | |
self._check_closed() | |
try: | |
return self._sentinel | |
except AttributeError: | |
raise ValueError("process not started") from None | |
def __repr__(self): | |
exitcode = None | |
if self is _current_process: | |
status = 'started' | |
elif self._closed: | |
status = 'closed' | |
elif self._parent_pid != os.getpid(): | |
status = 'unknown' | |
elif self._popen is None: | |
status = 'initial' | |
else: | |
exitcode = self._popen.poll() | |
if exitcode is not None: | |
status = 'stopped' | |
else: | |
status = 'started' | |
info = [type(self).__name__, 'name=%r' % self._name] | |
if self._popen is not None: | |
info.append('pid=%s' % self._popen.pid) | |
info.append('parent=%s' % self._parent_pid) | |
info.append(status) | |
if exitcode is not None: | |
exitcode = _exitcode_to_name.get(exitcode, exitcode) | |
info.append('exitcode=%s' % exitcode) | |
if self.daemon: | |
info.append('daemon') | |
return '<%s>' % ' '.join(info) | |
## | |
def _bootstrap(self, parent_sentinel=None): | |
from . import util, context | |
global _current_process, _parent_process, _process_counter, _children | |
try: | |
if self._start_method is not None: | |
context._force_start_method(self._start_method) | |
_process_counter = itertools.count(1) | |
_children = set() | |
util._close_stdin() | |
old_process = _current_process | |
_current_process = self | |
_parent_process = _ParentProcess( | |
self._parent_name, self._parent_pid, parent_sentinel) | |
if threading._HAVE_THREAD_NATIVE_ID: | |
threading.main_thread()._set_native_id() | |
try: | |
self._after_fork() | |
finally: | |
# delay finalization of the old process object until after | |
# _run_after_forkers() is executed | |
del old_process | |
util.info('child process calling self.run()') | |
try: | |
self.run() | |
exitcode = 0 | |
finally: | |
util._exit_function() | |
except SystemExit as e: | |
if e.code is None: | |
exitcode = 0 | |
elif isinstance(e.code, int): | |
exitcode = e.code | |
else: | |
sys.stderr.write(str(e.code) + '\n') | |
exitcode = 1 | |
except: | |
exitcode = 1 | |
import traceback | |
sys.stderr.write('Process %s:\n' % self.name) | |
traceback.print_exc() | |
finally: | |
threading._shutdown() | |
util.info('process exiting with exitcode %d' % exitcode) | |
util._flush_std_streams() | |
return exitcode | |
def _after_fork(): | |
from . import util | |
util._finalizer_registry.clear() | |
util._run_after_forkers() | |
# | |
# We subclass bytes to avoid accidental transmission of auth keys over network | |
# | |
class AuthenticationString(bytes): | |
def __reduce__(self): | |
from .context import get_spawning_popen | |
if get_spawning_popen() is None: | |
raise TypeError( | |
'Pickling an AuthenticationString object is ' | |
'disallowed for security reasons' | |
) | |
return AuthenticationString, (bytes(self),) | |
# | |
# Create object representing the parent process | |
# | |
class _ParentProcess(BaseProcess): | |
def __init__(self, name, pid, sentinel): | |
self._identity = () | |
self._name = name | |
self._pid = pid | |
self._parent_pid = None | |
self._popen = None | |
self._closed = False | |
self._sentinel = sentinel | |
self._config = {} | |
def is_alive(self): | |
from multiprocessing.connection import wait | |
return not wait([self._sentinel], timeout=0) | |
def ident(self): | |
return self._pid | |
def join(self, timeout=None): | |
''' | |
Wait until parent process terminates | |
''' | |
from multiprocessing.connection import wait | |
wait([self._sentinel], timeout=timeout) | |
pid = ident | |
# | |
# Create object representing the main process | |
# | |
class _MainProcess(BaseProcess): | |
def __init__(self): | |
self._identity = () | |
self._name = 'MainProcess' | |
self._parent_pid = None | |
self._popen = None | |
self._closed = False | |
self._config = {'authkey': AuthenticationString(os.urandom(32)), | |
'semprefix': '/mp'} | |
# Note that some versions of FreeBSD only allow named | |
# semaphores to have names of up to 14 characters. Therefore | |
# we choose a short prefix. | |
# | |
# On MacOSX in a sandbox it may be necessary to use a | |
# different prefix -- see #19478. | |
# | |
# Everything in self._config will be inherited by descendant | |
# processes. | |
def close(self): | |
pass | |
_parent_process = None | |
_current_process = _MainProcess() | |
_process_counter = itertools.count(1) | |
_children = set() | |
del _MainProcess | |
# | |
# Give names to some return codes | |
# | |
_exitcode_to_name = {} | |
for name, signum in list(signal.__dict__.items()): | |
if name[:3]=='SIG' and '_' not in name: | |
_exitcode_to_name[-signum] = f'-{name}' | |
# For debug and leak testing | |
_dangling = WeakSet() | |