Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

bpo-36888: Add multiprocessing.parent_process() #13247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8839770
ENH add parent_process in multiprocessing contexts
tomMoral May 10, 2019
bcdba73
FIX avoid error in Process.__repr__
pierreglaser May 16, 2019
98de001
CLN more logical attribute naming
pierreglaser May 16, 2019
9b67034
ENH implement ParentProcess.is_alive
pierreglaser May 16, 2019
23a2f3d
FIX pass parent_name to ParentProcess
pierreglaser May 16, 2019
5630de5
do not use current_process to create ParentProcess
pierreglaser May 16, 2019
be73255
TST test parent_process use-cases
pierreglaser May 16, 2019
faa3fb8
FIX do not close parent_sentinel on windows
pierreglaser May 16, 2019
cd2f241
FIX implement ParentProcess on windows
pierreglaser May 16, 2019
3d71d3f
MNT news entry
pierreglaser May 16, 2019
0ceffc8
FIX _main for forkserver
pierreglaser May 16, 2019
9382659
TST better tests
pierreglaser May 16, 2019
95dfc61
ENH use narrower permissions on windows
pierreglaser May 20, 2019
29898b5
FIX duplicate sentinel when necessary
pierreglaser May 20, 2019
566db0e
ENH listen to read end of a pipe in fork
pierreglaser May 20, 2019
a2310a7
FIX duplicate the parent sentinel in forkserver
pierreglaser May 20, 2019
b3829f6
ENH access parent_process from multiprocessing
pierreglaser May 20, 2019
133df9c
TST improve test coverage and readability
pierreglaser May 20, 2019
3c5d241
FIX pass correct fd to spawn on windows
pierreglaser May 20, 2019
4fe8467
DOC docs
pierreglaser May 20, 2019
a9c987c
CLN duplicate fd instead of not closing it
pierreglaser May 20, 2019
e23cc08
address review comments
pierreglaser May 20, 2019
7293d4c
FIX pass timeout correctly
pierreglaser May 20, 2019
80dcabf
address review comments
pierreglaser May 20, 2019
32aa640
CLN unnecessary attribute
pierreglaser May 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions 8 Doc/library/multiprocessing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,14 @@ Miscellaneous

An analogue of :func:`threading.current_thread`.

.. function:: parent_process()

Return the :class:`Process` object corresponding to the parent process of
the :func:`current_process`. For the main process, ``parent_process`` will
be ``None``.

.. versionadded:: 3.8

.. function:: freeze_support()

Add support for when a program which uses :mod:`multiprocessing` has been
Expand Down
1 change: 1 addition & 0 deletions 1 Lib/multiprocessing/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class BaseContext(object):
AuthenticationError = AuthenticationError

current_process = staticmethod(process.current_process)
parent_process = staticmethod(process.parent_process)
active_children = staticmethod(process.active_children)

def cpu_count(self):
Expand Down
3 changes: 2 additions & 1 deletion 3 Lib/multiprocessing/forkserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ def _serve_one(child_r, fds, unused_fds, handlers):
*_forkserver._inherited_fds) = fds

# Run process object received over pipe
code = spawn._main(child_r)
parent_sentinel = os.dup(child_r)
code = spawn._main(child_r, parent_sentinel)

return code

Expand Down
8 changes: 6 additions & 2 deletions 8 Lib/multiprocessing/popen_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,20 @@ def kill(self):
def _launch(self, process_obj):
code = 1
parent_r, child_w = os.pipe()
child_r, parent_w = os.pipe()
self.pid = os.fork()
if self.pid == 0:
try:
os.close(parent_r)
code = process_obj._bootstrap()
os.close(parent_w)
code = process_obj._bootstrap(parent_sentinel=child_r)
finally:
os._exit(code)
else:
os.close(child_w)
self.finalizer = util.Finalize(self, os.close, (parent_r,))
os.close(child_r)
self.finalizer = util.Finalize(self, util.close_fds,
(parent_r, parent_w,))
self.sentinel = parent_r

def close(self):
Expand Down
6 changes: 5 additions & 1 deletion 6 Lib/multiprocessing/popen_forkserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ def _launch(self, process_obj):
set_spawning_popen(None)

self.sentinel, w = forkserver.connect_to_new_process(self._fds)
self.finalizer = util.Finalize(self, os.close, (self.sentinel,))
# Keep a duplicate of the data pipe's write end as a sentinel of the
# parent process used by the child process.
_parent_w = os.dup(w)
self.finalizer = util.Finalize(self, util.close_fds,
(_parent_w, self.sentinel))
with open(w, 'wb', closefd=True) as f:
f.write(buf.getbuffer())
self.pid = forkserver.read_signed(self.sentinel)
Expand Down
10 changes: 7 additions & 3 deletions 10 Lib/multiprocessing/popen_spawn_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ def _launch(self, process_obj):
with open(parent_w, 'wb', closefd=False) as f:
f.write(fp.getbuffer())
finally:
if parent_r is not None:
self.finalizer = util.Finalize(self, os.close, (parent_r,))
for fd in (child_r, child_w, parent_w):
fds_to_close = []
for fd in (parent_r, parent_w):
if fd is not None:
fds_to_close.append(fd)
self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)

for fd in (child_r, child_w):
pitrou marked this conversation as resolved.
Show resolved Hide resolved
if fd is not None:
os.close(fd)
52 changes: 49 additions & 3 deletions 52 Lib/multiprocessing/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
# Licensed to PSF under a Contributor Agreement.
#

__all__ = ['BaseProcess', 'current_process', 'active_children']
__all__ = ['BaseProcess', 'current_process', 'active_children',
'parent_process']

#
# Imports
Expand Down Expand Up @@ -46,6 +47,13 @@ def active_children():
_cleanup()
return list(_children)


def parent_process():
'''
Return process object representing the parent process
'''
return _parent_process

#
#
#
Expand Down Expand Up @@ -76,6 +84,7 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
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
Expand Down Expand Up @@ -278,9 +287,9 @@ def __repr__(self):

##

def _bootstrap(self):
def _bootstrap(self, parent_sentinel=None):
from . import util, context
global _current_process, _process_counter, _children
global _current_process, _parent_process, _process_counter, _children

try:
if self._start_method is not None:
Expand All @@ -290,6 +299,8 @@ def _bootstrap(self):
util._close_stdin()
old_process = _current_process
_current_process = self
_parent_process = _ParentProcess(
self._parent_name, self._parent_pid, parent_sentinel)
try:
util._finalizer_registry.clear()
util._run_after_forkers()
Expand Down Expand Up @@ -337,6 +348,40 @@ def __reduce__(self):
)
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):
pitrou marked this conversation as resolved.
Show resolved Hide resolved
from multiprocessing.connection import wait
return not wait([self._sentinel], timeout=0)

@property
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
#
Expand Down Expand Up @@ -365,6 +410,7 @@ def close(self):
pass


_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
Expand Down
19 changes: 9 additions & 10 deletions 19 Lib/multiprocessing/spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,24 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):

if parent_pid is not None:
source_process = _winapi.OpenProcess(
_winapi.PROCESS_DUP_HANDLE, False, parent_pid)
_winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
False, parent_pid)
else:
source_process = None
try:
new_handle = reduction.duplicate(pipe_handle,
source_process=source_process)
finally:
if source_process is not None:
_winapi.CloseHandle(source_process)
new_handle = reduction.duplicate(pipe_handle,
source_process=source_process)
fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
parent_sentinel = source_process
else:
from . import resource_tracker
resource_tracker._resource_tracker._fd = tracker_fd
fd = pipe_handle
exitcode = _main(fd)
parent_sentinel = os.dup(pipe_handle)
exitcode = _main(fd, parent_sentinel)
sys.exit(exitcode)


def _main(fd):
def _main(fd, parent_sentinel):
with os.fdopen(fd, 'rb', closefd=True) as from_parent:
process.current_process()._inheriting = True
try:
Expand All @@ -127,7 +126,7 @@ def _main(fd):
self = reduction.pickle.load(from_parent)
finally:
del process.current_process()._inheriting
return self._bootstrap()
return self._bootstrap(parent_sentinel)


def _check_not_importing_main():
Expand Down
6 changes: 6 additions & 0 deletions 6 Lib/multiprocessing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,9 @@ def spawnv_passfds(path, args, passfds):
finally:
os.close(errpipe_read)
os.close(errpipe_write)


def close_fds(*fds):
"""Close each file descriptor given as an argument"""
for fd in fds:
os.close(fd)
59 changes: 59 additions & 0 deletions 59 Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,64 @@ def _test(cls, q, *args, **kwds):
q.put(bytes(current.authkey))
q.put(current.pid)

def test_parent_process_attributes(self):
if self.TYPE == "threads":
self.skipTest('test not appropriate for {}'.format(self.TYPE))

self.assertIsNone(self.parent_process())

rconn, wconn = self.Pipe(duplex=False)
p = self.Process(target=self._test_send_parent_process, args=(wconn,))
p.start()
p.join()
parent_pid, parent_name = rconn.recv()
self.assertEqual(parent_pid, self.current_process().pid)
self.assertEqual(parent_pid, os.getpid())
self.assertEqual(parent_name, self.current_process().name)

@classmethod
def _test_send_parent_process(cls, wconn):
from multiprocessing.process import parent_process
wconn.send([parent_process().pid, parent_process().name])

def test_parent_process(self):
if self.TYPE == "threads":
self.skipTest('test not appropriate for {}'.format(self.TYPE))

# Launch a child process. Make it launch a grandchild process. Kill the
# child process and make sure that the grandchild notices the death of
# its parent (a.k.a the child process).
rconn, wconn = self.Pipe(duplex=False)
p = self.Process(
target=self._test_create_grandchild_process, args=(wconn, ))
p.start()

if not rconn.poll(timeout=5):
raise AssertionError("Could not communicate with child process")
parent_process_status = rconn.recv()
self.assertEqual(parent_process_status, "alive")

p.terminate()
p.join()

if not rconn.poll(timeout=5):
raise AssertionError("Could not communicate with child process")
parent_process_status = rconn.recv()
self.assertEqual(parent_process_status, "not alive")

@classmethod
def _test_create_grandchild_process(cls, wconn):
p = cls.Process(target=cls._test_report_parent_status, args=(wconn, ))
tomMoral marked this conversation as resolved.
Show resolved Hide resolved
p.start()
time.sleep(100)

@classmethod
def _test_report_parent_status(cls, wconn):
from multiprocessing.process import parent_process
wconn.send("alive" if parent_process().is_alive() else "not alive")
parent_process().join(timeout=5)
wconn.send("alive" if parent_process().is_alive() else "not alive")

def test_process(self):
q = self.Queue(1)
e = self.Event()
Expand Down Expand Up @@ -5390,6 +5448,7 @@ class ProcessesMixin(BaseMixin):
Process = multiprocessing.Process
connection = multiprocessing.connection
current_process = staticmethod(multiprocessing.current_process)
parent_process = staticmethod(multiprocessing.parent_process)
active_children = staticmethod(multiprocessing.active_children)
Pool = staticmethod(multiprocessing.Pool)
Pipe = staticmethod(multiprocessing.Pipe)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Python child processes can now access the status of their parent process
using multiprocessing.process.parent_process
1 change: 1 addition & 0 deletions 1 Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1955,6 +1955,7 @@ PyInit__winapi(void)
WINAPI_CONSTANT(F_DWORD, PIPE_UNLIMITED_INSTANCES);
WINAPI_CONSTANT(F_DWORD, PIPE_WAIT);
WINAPI_CONSTANT(F_DWORD, PROCESS_ALL_ACCESS);
WINAPI_CONSTANT(F_DWORD, SYNCHRONIZE);
WINAPI_CONSTANT(F_DWORD, PROCESS_DUP_HANDLE);
WINAPI_CONSTANT(F_DWORD, SEC_COMMIT);
WINAPI_CONSTANT(F_DWORD, SEC_IMAGE);
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.