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

Commit c09a9f5

Browse filesBrowse files
tomMoralpitrou
authored andcommitted
bpo-36888: Add multiprocessing.parent_process() (GH-13247)
1 parent 5ae1c84 commit c09a9f5
Copy full SHA for c09a9f5

File tree

Expand file treeCollapse file tree

12 files changed

+155
-20
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+155
-20
lines changed

‎Doc/library/multiprocessing.rst

Copy file name to clipboardExpand all lines: Doc/library/multiprocessing.rst
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,14 @@ Miscellaneous
944944

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

947+
.. function:: parent_process()
948+
949+
Return the :class:`Process` object corresponding to the parent process of
950+
the :func:`current_process`. For the main process, ``parent_process`` will
951+
be ``None``.
952+
953+
.. versionadded:: 3.8
954+
947955
.. function:: freeze_support()
948956

949957
Add support for when a program which uses :mod:`multiprocessing` has been

‎Lib/multiprocessing/context.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/context.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class BaseContext(object):
3535
AuthenticationError = AuthenticationError
3636

3737
current_process = staticmethod(process.current_process)
38+
parent_process = staticmethod(process.parent_process)
3839
active_children = staticmethod(process.active_children)
3940

4041
def cpu_count(self):

‎Lib/multiprocessing/forkserver.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/forkserver.py
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ def _serve_one(child_r, fds, unused_fds, handlers):
294294
*_forkserver._inherited_fds) = fds
295295

296296
# Run process object received over pipe
297-
code = spawn._main(child_r)
297+
parent_sentinel = os.dup(child_r)
298+
code = spawn._main(child_r, parent_sentinel)
298299

299300
return code
300301

‎Lib/multiprocessing/popen_fork.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/popen_fork.py
+6-2Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,20 @@ def kill(self):
6666
def _launch(self, process_obj):
6767
code = 1
6868
parent_r, child_w = os.pipe()
69+
child_r, parent_w = os.pipe()
6970
self.pid = os.fork()
7071
if self.pid == 0:
7172
try:
7273
os.close(parent_r)
73-
code = process_obj._bootstrap()
74+
os.close(parent_w)
75+
code = process_obj._bootstrap(parent_sentinel=child_r)
7476
finally:
7577
os._exit(code)
7678
else:
7779
os.close(child_w)
78-
self.finalizer = util.Finalize(self, os.close, (parent_r,))
80+
os.close(child_r)
81+
self.finalizer = util.Finalize(self, util.close_fds,
82+
(parent_r, parent_w,))
7983
self.sentinel = parent_r
8084

8185
def close(self):

‎Lib/multiprocessing/popen_forkserver.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/popen_forkserver.py
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ def _launch(self, process_obj):
4949
set_spawning_popen(None)
5050

5151
self.sentinel, w = forkserver.connect_to_new_process(self._fds)
52-
self.finalizer = util.Finalize(self, os.close, (self.sentinel,))
52+
# Keep a duplicate of the data pipe's write end as a sentinel of the
53+
# parent process used by the child process.
54+
_parent_w = os.dup(w)
55+
self.finalizer = util.Finalize(self, util.close_fds,
56+
(_parent_w, self.sentinel))
5357
with open(w, 'wb', closefd=True) as f:
5458
f.write(buf.getbuffer())
5559
self.pid = forkserver.read_signed(self.sentinel)

‎Lib/multiprocessing/popen_spawn_posix.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/popen_spawn_posix.py
+7-3Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,12 @@ def _launch(self, process_obj):
6161
with open(parent_w, 'wb', closefd=False) as f:
6262
f.write(fp.getbuffer())
6363
finally:
64-
if parent_r is not None:
65-
self.finalizer = util.Finalize(self, os.close, (parent_r,))
66-
for fd in (child_r, child_w, parent_w):
64+
fds_to_close = []
65+
for fd in (parent_r, parent_w):
66+
if fd is not None:
67+
fds_to_close.append(fd)
68+
self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)
69+
70+
for fd in (child_r, child_w):
6771
if fd is not None:
6872
os.close(fd)

‎Lib/multiprocessing/process.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/process.py
+49-3Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
# Licensed to PSF under a Contributor Agreement.
88
#
99

10-
__all__ = ['BaseProcess', 'current_process', 'active_children']
10+
__all__ = ['BaseProcess', 'current_process', 'active_children',
11+
'parent_process']
1112

1213
#
1314
# Imports
@@ -46,6 +47,13 @@ def active_children():
4647
_cleanup()
4748
return list(_children)
4849

50+
51+
def parent_process():
52+
'''
53+
Return process object representing the parent process
54+
'''
55+
return _parent_process
56+
4957
#
5058
#
5159
#
@@ -76,6 +84,7 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
7684
self._identity = _current_process._identity + (count,)
7785
self._config = _current_process._config.copy()
7886
self._parent_pid = os.getpid()
87+
self._parent_name = _current_process.name
7988
self._popen = None
8089
self._closed = False
8190
self._target = target
@@ -278,9 +287,9 @@ def __repr__(self):
278287

279288
##
280289

281-
def _bootstrap(self):
290+
def _bootstrap(self, parent_sentinel=None):
282291
from . import util, context
283-
global _current_process, _process_counter, _children
292+
global _current_process, _parent_process, _process_counter, _children
284293

285294
try:
286295
if self._start_method is not None:
@@ -290,6 +299,8 @@ def _bootstrap(self):
290299
util._close_stdin()
291300
old_process = _current_process
292301
_current_process = self
302+
_parent_process = _ParentProcess(
303+
self._parent_name, self._parent_pid, parent_sentinel)
293304
try:
294305
util._finalizer_registry.clear()
295306
util._run_after_forkers()
@@ -337,6 +348,40 @@ def __reduce__(self):
337348
)
338349
return AuthenticationString, (bytes(self),)
339350

351+
352+
#
353+
# Create object representing the parent process
354+
#
355+
356+
class _ParentProcess(BaseProcess):
357+
358+
def __init__(self, name, pid, sentinel):
359+
self._identity = ()
360+
self._name = name
361+
self._pid = pid
362+
self._parent_pid = None
363+
self._popen = None
364+
self._closed = False
365+
self._sentinel = sentinel
366+
self._config = {}
367+
368+
def is_alive(self):
369+
from multiprocessing.connection import wait
370+
return not wait([self._sentinel], timeout=0)
371+
372+
@property
373+
def ident(self):
374+
return self._pid
375+
376+
def join(self, timeout=None):
377+
'''
378+
Wait until parent process terminates
379+
'''
380+
from multiprocessing.connection import wait
381+
wait([self._sentinel], timeout=timeout)
382+
383+
pid = ident
384+
340385
#
341386
# Create object representing the main process
342387
#
@@ -365,6 +410,7 @@ def close(self):
365410
pass
366411

367412

413+
_parent_process = None
368414
_current_process = _MainProcess()
369415
_process_counter = itertools.count(1)
370416
_children = set()

‎Lib/multiprocessing/spawn.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/spawn.py
+9-10Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,25 +100,24 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
100100

101101
if parent_pid is not None:
102102
source_process = _winapi.OpenProcess(
103-
_winapi.PROCESS_DUP_HANDLE, False, parent_pid)
103+
_winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
104+
False, parent_pid)
104105
else:
105106
source_process = None
106-
try:
107-
new_handle = reduction.duplicate(pipe_handle,
108-
source_process=source_process)
109-
finally:
110-
if source_process is not None:
111-
_winapi.CloseHandle(source_process)
107+
new_handle = reduction.duplicate(pipe_handle,
108+
source_process=source_process)
112109
fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
110+
parent_sentinel = source_process
113111
else:
114112
from . import resource_tracker
115113
resource_tracker._resource_tracker._fd = tracker_fd
116114
fd = pipe_handle
117-
exitcode = _main(fd)
115+
parent_sentinel = os.dup(pipe_handle)
116+
exitcode = _main(fd, parent_sentinel)
118117
sys.exit(exitcode)
119118

120119

121-
def _main(fd):
120+
def _main(fd, parent_sentinel):
122121
with os.fdopen(fd, 'rb', closefd=True) as from_parent:
123122
process.current_process()._inheriting = True
124123
try:
@@ -127,7 +126,7 @@ def _main(fd):
127126
self = reduction.pickle.load(from_parent)
128127
finally:
129128
del process.current_process()._inheriting
130-
return self._bootstrap()
129+
return self._bootstrap(parent_sentinel)
131130

132131

133132
def _check_not_importing_main():

‎Lib/multiprocessing/util.py

Copy file name to clipboardExpand all lines: Lib/multiprocessing/util.py
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,9 @@ def spawnv_passfds(path, args, passfds):
421421
finally:
422422
os.close(errpipe_read)
423423
os.close(errpipe_write)
424+
425+
426+
def close_fds(*fds):
427+
"""Close each file descriptor given as an argument"""
428+
for fd in fds:
429+
os.close(fd)

‎Lib/test/_test_multiprocessing.py

Copy file name to clipboardExpand all lines: Lib/test/_test_multiprocessing.py
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,64 @@ def _test(cls, q, *args, **kwds):
269269
q.put(bytes(current.authkey))
270270
q.put(current.pid)
271271

272+
def test_parent_process_attributes(self):
273+
if self.TYPE == "threads":
274+
self.skipTest('test not appropriate for {}'.format(self.TYPE))
275+
276+
self.assertIsNone(self.parent_process())
277+
278+
rconn, wconn = self.Pipe(duplex=False)
279+
p = self.Process(target=self._test_send_parent_process, args=(wconn,))
280+
p.start()
281+
p.join()
282+
parent_pid, parent_name = rconn.recv()
283+
self.assertEqual(parent_pid, self.current_process().pid)
284+
self.assertEqual(parent_pid, os.getpid())
285+
self.assertEqual(parent_name, self.current_process().name)
286+
287+
@classmethod
288+
def _test_send_parent_process(cls, wconn):
289+
from multiprocessing.process import parent_process
290+
wconn.send([parent_process().pid, parent_process().name])
291+
292+
def test_parent_process(self):
293+
if self.TYPE == "threads":
294+
self.skipTest('test not appropriate for {}'.format(self.TYPE))
295+
296+
# Launch a child process. Make it launch a grandchild process. Kill the
297+
# child process and make sure that the grandchild notices the death of
298+
# its parent (a.k.a the child process).
299+
rconn, wconn = self.Pipe(duplex=False)
300+
p = self.Process(
301+
target=self._test_create_grandchild_process, args=(wconn, ))
302+
p.start()
303+
304+
if not rconn.poll(timeout=5):
305+
raise AssertionError("Could not communicate with child process")
306+
parent_process_status = rconn.recv()
307+
self.assertEqual(parent_process_status, "alive")
308+
309+
p.terminate()
310+
p.join()
311+
312+
if not rconn.poll(timeout=5):
313+
raise AssertionError("Could not communicate with child process")
314+
parent_process_status = rconn.recv()
315+
self.assertEqual(parent_process_status, "not alive")
316+
317+
@classmethod
318+
def _test_create_grandchild_process(cls, wconn):
319+
p = cls.Process(target=cls._test_report_parent_status, args=(wconn, ))
320+
p.start()
321+
time.sleep(100)
322+
323+
@classmethod
324+
def _test_report_parent_status(cls, wconn):
325+
from multiprocessing.process import parent_process
326+
wconn.send("alive" if parent_process().is_alive() else "not alive")
327+
parent_process().join(timeout=5)
328+
wconn.send("alive" if parent_process().is_alive() else "not alive")
329+
272330
def test_process(self):
273331
q = self.Queue(1)
274332
e = self.Event()
@@ -5398,6 +5456,7 @@ class ProcessesMixin(BaseMixin):
53985456
Process = multiprocessing.Process
53995457
connection = multiprocessing.connection
54005458
current_process = staticmethod(multiprocessing.current_process)
5459+
parent_process = staticmethod(multiprocessing.parent_process)
54015460
active_children = staticmethod(multiprocessing.active_children)
54025461
Pool = staticmethod(multiprocessing.Pool)
54035462
Pipe = staticmethod(multiprocessing.Pipe)
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Python child processes can now access the status of their parent process
2+
using multiprocessing.process.parent_process

‎Modules/_winapi.c

Copy file name to clipboardExpand all lines: Modules/_winapi.c
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,7 @@ PyInit__winapi(void)
19551955
WINAPI_CONSTANT(F_DWORD, PIPE_UNLIMITED_INSTANCES);
19561956
WINAPI_CONSTANT(F_DWORD, PIPE_WAIT);
19571957
WINAPI_CONSTANT(F_DWORD, PROCESS_ALL_ACCESS);
1958+
WINAPI_CONSTANT(F_DWORD, SYNCHRONIZE);
19581959
WINAPI_CONSTANT(F_DWORD, PROCESS_DUP_HANDLE);
19591960
WINAPI_CONSTANT(F_DWORD, SEC_COMMIT);
19601961
WINAPI_CONSTANT(F_DWORD, SEC_IMAGE);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.