Fix bpo-30596: Add close() method to multiprocessing.Process (#2010)

* Fix bpo-30596: Add close() method to multiprocessing.Process

* Raise ValueError if close() is called before the Process is finished running

* Add docs

* Add NEWS blurb
This commit is contained in:
Antoine Pitrou 2017-06-24 19:22:23 +02:00 committed by GitHub
parent 0ee32c1481
commit 13e96cc596
9 changed files with 106 additions and 8 deletions

View file

@ -76,6 +76,7 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
self._config = _current_process._config.copy()
self._parent_pid = os.getpid()
self._popen = None
self._closed = False
self._target = target
self._args = tuple(args)
self._kwargs = dict(kwargs)
@ -85,6 +86,10 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
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
@ -96,6 +101,7 @@ 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'
@ -110,12 +116,14 @@ def terminate(self):
'''
Terminate process; sends SIGTERM signal or uses TerminateProcess()
'''
self._check_closed()
self._popen.terminate()
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)
@ -126,6 +134,7 @@ 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'
@ -134,6 +143,23 @@ def is_alive(self):
self._popen.poll()
return self._popen.returncode is None
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
@property
def name(self):
return self._name
@ -174,6 +200,7 @@ 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()
@ -183,6 +210,7 @@ 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:
@ -196,6 +224,7 @@ 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:
@ -204,6 +233,8 @@ def sentinel(self):
def __repr__(self):
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:
@ -295,6 +326,7 @@ def __init__(self):
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
@ -307,6 +339,9 @@ def __init__(self):
# Everything in self._config will be inherited by descendant
# processes.
def close(self):
pass
_current_process = _MainProcess()
_process_counter = itertools.count(1)