Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c09a9f5

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

File tree

12 files changed

+155
-20
lines changed

12 files changed

+155
-20
lines changed

Doc/library/multiprocessing.rst

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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)

0 commit comments

Comments
 (0)