From 883977069a1faae14f1bbfb7ec59bb0c65491cb0 Mon Sep 17 00:00:00 2001 From: tomMoral Date: Fri, 10 May 2019 22:32:27 +0200 Subject: [PATCH 01/25] ENH add parent_process in multiprocessing contexts --- Lib/multiprocessing/popen_fork.py | 2 +- Lib/multiprocessing/process.py | 36 ++++++++++++++++++++++++++++--- Lib/multiprocessing/spawn.py | 4 ++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 685e8daf77ca1f..ac8dc58e944e50 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -70,7 +70,7 @@ def _launch(self, process_obj): if self.pid == 0: try: os.close(parent_r) - code = process_obj._bootstrap() + code = process_obj._bootstrap(parent_sentinel=child_w) finally: os._exit(code) else: diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 780f2d0c273472..e7a4d97d9ca140 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -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 @@ -46,6 +47,13 @@ def active_children(): _cleanup() return list(_children) + +def parent_process(): + ''' + Return process object representing the parent process + ''' + return _parent_process + # # # @@ -278,9 +286,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: @@ -289,6 +297,8 @@ def _bootstrap(self): _children = set() util._close_stdin() old_process = _current_process + _parent_process = _ParentProcess(old_process.name, old_process.pid, + parent_sentinel) _current_process = self try: util._finalizer_registry.clear() @@ -337,6 +347,25 @@ 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._parent_pid = pid + self._popen = None + self._closed = False + self._sentinel = sentinel + + def close(self): + pass + + # # Create object representing the main process # @@ -365,6 +394,7 @@ def close(self): pass +_parent_process = None _current_process = _MainProcess() _process_counter = itertools.count(1) _children = set() diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index f66b5aa9267b6d..06ed2a942f817d 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -119,7 +119,7 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): def _main(fd): - with os.fdopen(fd, 'rb', closefd=True) as from_parent: + with os.fdopen(fd, 'rb', closefd=False) as from_parent: process.current_process()._inheriting = True try: preparation_data = reduction.pickle.load(from_parent) @@ -127,7 +127,7 @@ def _main(fd): self = reduction.pickle.load(from_parent) finally: del process.current_process()._inheriting - return self._bootstrap() + return self._bootstrap(parent_sentinel=fd) def _check_not_importing_main(): From bcdba7375e6cb98398e2249bb2f3dcd1fdcc8858 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:30:42 +0200 Subject: [PATCH 02/25] FIX avoid error in Process.__repr__ --- Lib/multiprocessing/process.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index e7a4d97d9ca140..589addcc4586ca 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -361,6 +361,7 @@ def __init__(self, name, pid, sentinel): self._popen = None self._closed = False self._sentinel = sentinel + self._config = {} def close(self): pass From 98de00158885adffd0a2c1e5a01212e2a3c92b55 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:35:54 +0200 Subject: [PATCH 03/25] CLN more logical attribute naming --- Lib/multiprocessing/process.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 589addcc4586ca..1e48ada2662b90 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -357,7 +357,8 @@ class _ParentProcess(BaseProcess): def __init__(self, name, pid, sentinel): self._identity = () self._name = name - self._parent_pid = pid + self._pid = pid + self._parent_pid = None self._popen = None self._closed = False self._sentinel = sentinel @@ -366,6 +367,11 @@ def __init__(self, name, pid, sentinel): def close(self): pass + @property + def ident(self): + return self._pid + + pid = ident # # Create object representing the main process From 9b6703496731365cb34390f9f90de0f0c983078f Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:36:40 +0200 Subject: [PATCH 04/25] ENH implement ParentProcess.is_alive --- Lib/multiprocessing/popen_forkserver.py | 2 +- Lib/multiprocessing/popen_spawn_posix.py | 2 +- Lib/multiprocessing/process.py | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/multiprocessing/popen_forkserver.py b/Lib/multiprocessing/popen_forkserver.py index a51a2771aed8cc..a6915b7da5bd81 100644 --- a/Lib/multiprocessing/popen_forkserver.py +++ b/Lib/multiprocessing/popen_forkserver.py @@ -50,7 +50,7 @@ def _launch(self, process_obj): self.sentinel, w = forkserver.connect_to_new_process(self._fds) self.finalizer = util.Finalize(self, os.close, (self.sentinel,)) - with open(w, 'wb', closefd=True) as f: + with open(w, 'wb', closefd=False) as f: f.write(buf.getbuffer()) self.pid = forkserver.read_signed(self.sentinel) diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py index 59f8e452cae1d5..d8ce4cbe54031c 100644 --- a/Lib/multiprocessing/popen_spawn_posix.py +++ b/Lib/multiprocessing/popen_spawn_posix.py @@ -63,6 +63,6 @@ def _launch(self, process_obj): 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): + for fd in (child_r, child_w): if fd is not None: os.close(fd) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 1e48ada2662b90..e8a6d39df63418 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -367,6 +367,10 @@ def __init__(self, name, pid, sentinel): def close(self): pass + def is_alive(self): + from multiprocessing.connection import wait + return not wait([self._sentinel], timeout=0) + @property def ident(self): return self._pid From 23a2f3d1ad65aaf48029927db92ecaa34fba5c3f Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:39:05 +0200 Subject: [PATCH 05/25] FIX pass parent_name to ParentProcess --- Lib/multiprocessing/process.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index e8a6d39df63418..a975cc7e78a41e 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -84,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 From 5630de589d4255a00c4fbf65dcedabb53b798471 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:39:35 +0200 Subject: [PATCH 06/25] do not use current_process to create ParentProcess --- Lib/multiprocessing/process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index a975cc7e78a41e..0364ea140abe3f 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -298,9 +298,9 @@ def _bootstrap(self, parent_sentinel=None): _children = set() util._close_stdin() old_process = _current_process - _parent_process = _ParentProcess(old_process.name, old_process.pid, - parent_sentinel) _current_process = self + _parent_process = _ParentProcess( + self._parent_name, self._parent_pid, parent_sentinel) try: util._finalizer_registry.clear() util._run_after_forkers() From be73255a72991b23f3f9f10945458bdb4b7e1eee Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:40:18 +0200 Subject: [PATCH 07/25] TST test parent_process use-cases --- Lib/test/_test_multiprocessing.py | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 772c9638337ae5..5d117bcd441c02 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -269,6 +269,62 @@ 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)) + from multiprocessing.process import current_process + rconn, wconn = self.Pipe(duplex=False) + p = self.Process(target=self._test_send_parent_process, args=(wconn,)) + p.start() + p.join() + parent_process = rconn.recv() + assert parent_process.pid == current_process().pid + assert parent_process.name == current_process().name + + @classmethod + def _test_send_parent_process(cls, wconn): + from multiprocessing.process import parent_process + wconn.send(parent_process()) + + 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() + time.sleep(1) + p.terminate() + p.join() + parent_process_status_log = rconn.recv() + assert parent_process_status_log == ["alive", "not alive"] + + @classmethod + def _test_create_grandchild_process(cls, wconn): + p = cls.Process(target=cls._test_report_parent_status, args=(wconn, )) + p.start() + time.sleep(100) + + @classmethod + def _test_report_parent_status(cls, wconn): + from multiprocessing.process import parent_process + status_log = [] + status_log.append( + "alive" if parent_process().is_alive() else "not alive") + + start_time = time.monotonic() + while (parent_process().is_alive() and + (time.monotonic() - start_time) < 5): + time.sleep(0.1) + + status_log.append( + "alive" if parent_process().is_alive() else "not alive") + wconn.send(status_log) + def test_process(self): q = self.Queue(1) e = self.Event() From faa3fb8f835ec8b0f2a44a0dd3048dfab127ece0 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 14:51:29 +0200 Subject: [PATCH 08/25] FIX do not close parent_sentinel on windows --- Lib/multiprocessing/popen_spawn_win32.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index de4c5ecf1fa083..f7e0bdcac3ca3b 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -68,7 +68,7 @@ def __init__(self, process_obj): else: env = None - with open(wfd, 'wb', closefd=True) as to_child: + with open(wfd, 'wb', closefd=False) as to_child: # start process try: hp, ht, pid, tid = _winapi.CreateProcess( From cd2f241a3ca1cefe3c784af62b6d99ebd45ca1d3 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 07:12:59 -0700 Subject: [PATCH 09/25] FIX implement ParentProcess on windows --- Lib/multiprocessing/spawn.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index 06ed2a942f817d..20fdb008482a2d 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -100,25 +100,22 @@ 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.PROCESS_ALL_ACCESS, 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) + fd = parent_sentinel = pipe_handle + exitcode = _main(fd, parent_sentinel) sys.exit(exitcode) -def _main(fd): +def _main(fd, parent_sentinel): with os.fdopen(fd, 'rb', closefd=False) as from_parent: process.current_process()._inheriting = True try: @@ -127,7 +124,7 @@ def _main(fd): self = reduction.pickle.load(from_parent) finally: del process.current_process()._inheriting - return self._bootstrap(parent_sentinel=fd) + return self._bootstrap(parent_sentinel) def _check_not_importing_main(): From 3d71d3f0dce1649aa724c4f94e9c8d53b954a7d4 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 18:02:16 +0200 Subject: [PATCH 10/25] MNT news entry --- .../next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst diff --git a/Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst b/Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst new file mode 100644 index 00000000000000..e7b54677280c9f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst @@ -0,0 +1,2 @@ +Python child processes can now access the status of their parent process +using multiprocessing.process.parent_process From 0ceffc8bd7a431b5558ed76d6551d9f8dc3616fa Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 18:53:56 +0200 Subject: [PATCH 11/25] FIX _main for forkserver --- Lib/multiprocessing/forkserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index dabf7bcbe6d785..cdcb24f8d297f1 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -294,7 +294,7 @@ def _serve_one(child_r, fds, unused_fds, handlers): *_forkserver._inherited_fds) = fds # Run process object received over pipe - code = spawn._main(child_r) + code = spawn._main(child_r, child_r) return code From 9382659078dd55644c9b69103cce25d93479aa51 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Thu, 16 May 2019 18:55:55 +0200 Subject: [PATCH 12/25] TST better tests --- Lib/test/_test_multiprocessing.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 5d117bcd441c02..f419a1f658979d 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -277,14 +277,15 @@ def test_parent_process_attributes(self): p = self.Process(target=self._test_send_parent_process, args=(wconn,)) p.start() p.join() - parent_process = rconn.recv() - assert parent_process.pid == current_process().pid - assert parent_process.name == current_process().name + parent_pid, parent_name = rconn.recv() + self.assertEqual(parent_pid, current_process().pid) + self.assertEqual(parent_pid, os.getpid()) + self.assertEqual(parent_name, current_process().name) @classmethod def _test_send_parent_process(cls, wconn): from multiprocessing.process import parent_process - wconn.send(parent_process()) + wconn.send([parent_process().pid, parent_process().name]) def test_parent_process(self): if self.TYPE == "threads": @@ -297,11 +298,19 @@ def test_parent_process(self): p = self.Process( target=self._test_create_grandchild_process, args=(wconn, )) p.start() - time.sleep(1) + + 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() - parent_process_status_log = rconn.recv() - assert parent_process_status_log == ["alive", "not alive"] + + 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): @@ -312,18 +321,14 @@ def _test_create_grandchild_process(cls, wconn): @classmethod def _test_report_parent_status(cls, wconn): from multiprocessing.process import parent_process - status_log = [] - status_log.append( - "alive" if parent_process().is_alive() else "not alive") + wconn.send("alive" if parent_process().is_alive() else "not alive") start_time = time.monotonic() while (parent_process().is_alive() and (time.monotonic() - start_time) < 5): time.sleep(0.1) - status_log.append( - "alive" if parent_process().is_alive() else "not alive") - wconn.send(status_log) + wconn.send("alive" if parent_process().is_alive() else "not alive") def test_process(self): q = self.Queue(1) From 95dfc61ff45abc0982938eb3b894dddfbdf98a4e Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 01:42:17 -0700 Subject: [PATCH 13/25] ENH use narrower permissions on windows --- Lib/multiprocessing/spawn.py | 3 ++- Modules/_winapi.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index 20fdb008482a2d..dd0cd45ea0ffd8 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -100,7 +100,8 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): if parent_pid is not None: source_process = _winapi.OpenProcess( - _winapi.PROCESS_ALL_ACCESS, False, parent_pid) + _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE, + False, parent_pid) else: source_process = None new_handle = reduction.duplicate(pipe_handle, diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 2eb708e9073e91..8873519e6ce5df 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -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); From 29898b532d1ee567ffc70b8adbed88eb969a352d Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 11:01:13 +0200 Subject: [PATCH 14/25] FIX duplicate sentinel when necessary --- Lib/multiprocessing/spawn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index dd0cd45ea0ffd8..850184a1b95f0d 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -111,13 +111,13 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): else: from . import resource_tracker resource_tracker._resource_tracker._fd = tracker_fd - fd = parent_sentinel = pipe_handle - exitcode = _main(fd, parent_sentinel) + parent_sentinel = os.dup(pipe_handle) + exitcode = _main(pipe_handle, parent_sentinel) sys.exit(exitcode) def _main(fd, parent_sentinel): - with os.fdopen(fd, 'rb', closefd=False) as from_parent: + with os.fdopen(fd, 'rb', closefd=True) as from_parent: process.current_process()._inheriting = True try: preparation_data = reduction.pickle.load(from_parent) From 566db0e941c3c80433ac52f996aa310a64264771 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 11:40:51 +0200 Subject: [PATCH 15/25] ENH listen to read end of a pipe in fork --- Lib/multiprocessing/popen_fork.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index ac8dc58e944e50..4ec924e1f23aab 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -66,15 +66,18 @@ 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(parent_sentinel=child_w) + os.close(parent_w) + code = process_obj._bootstrap(parent_sentinel=child_r) finally: os._exit(code) else: os.close(child_w) + os.close(child_r) self.finalizer = util.Finalize(self, os.close, (parent_r,)) self.sentinel = parent_r From a2310a78f8499139065a869509bef3597c2b79cd Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 12:23:41 +0200 Subject: [PATCH 16/25] FIX duplicate the parent sentinel in forkserver --- Lib/multiprocessing/forkserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index cdcb24f8d297f1..9b6398671dbc5e 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -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, child_r) + parent_sentinel = os.dup(child_r) + code = spawn._main(child_r, parent_sentinel) return code From b3829f62ad00447c3023a9ef51d289adb6eb4884 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 12:26:40 +0200 Subject: [PATCH 17/25] ENH access parent_process from multiprocessing --- Lib/multiprocessing/context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py index 871746b1a047b3..5a4865751c2272 100644 --- a/Lib/multiprocessing/context.py +++ b/Lib/multiprocessing/context.py @@ -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): From 133df9c56b948bb684f8729811228493bec52bcc Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 12:35:55 +0200 Subject: [PATCH 18/25] TST improve test coverage and readability --- Lib/test/_test_multiprocessing.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index f419a1f658979d..c70c93b2b443b7 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -272,15 +272,17 @@ def _test(cls, q, *args, **kwds): def test_parent_process_attributes(self): if self.TYPE == "threads": self.skipTest('test not appropriate for {}'.format(self.TYPE)) - from multiprocessing.process import current_process + + 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, current_process().pid) + self.assertEqual(parent_pid, self.current_process().pid) self.assertEqual(parent_pid, os.getpid()) - self.assertEqual(parent_name, current_process().name) + self.assertEqual(parent_name, self.current_process().name) @classmethod def _test_send_parent_process(cls, wconn): @@ -5451,6 +5453,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) From 3c5d2418ed874ea2b69a47025767739da1a91f8e Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 14:00:05 +0200 Subject: [PATCH 19/25] FIX pass correct fd to spawn on windows --- Lib/multiprocessing/spawn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py index 850184a1b95f0d..7cc129e2610761 100644 --- a/Lib/multiprocessing/spawn.py +++ b/Lib/multiprocessing/spawn.py @@ -111,8 +111,9 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None): else: from . import resource_tracker resource_tracker._resource_tracker._fd = tracker_fd + fd = pipe_handle parent_sentinel = os.dup(pipe_handle) - exitcode = _main(pipe_handle, parent_sentinel) + exitcode = _main(fd, parent_sentinel) sys.exit(exitcode) From 4fe8467221b0776e3bf29cff4d3ac47a1e73177d Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 14:32:30 +0200 Subject: [PATCH 20/25] DOC docs --- Doc/library/multiprocessing.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index c6ffb00819c32c..cc6dd4e9d70226 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -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 From a9c987ce79b05b2330a54e0b2a84db8e3a3fc66a Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 14:41:51 +0200 Subject: [PATCH 21/25] CLN duplicate fd instead of not closing it --- Lib/multiprocessing/popen_forkserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/multiprocessing/popen_forkserver.py b/Lib/multiprocessing/popen_forkserver.py index a6915b7da5bd81..aba61a30b47030 100644 --- a/Lib/multiprocessing/popen_forkserver.py +++ b/Lib/multiprocessing/popen_forkserver.py @@ -50,7 +50,8 @@ def _launch(self, process_obj): self.sentinel, w = forkserver.connect_to_new_process(self._fds) self.finalizer = util.Finalize(self, os.close, (self.sentinel,)) - with open(w, 'wb', closefd=False) as f: + parent_sentinel = os.dup(w) + with open(w, 'wb', closefd=True) as f: f.write(buf.getbuffer()) self.pid = forkserver.read_signed(self.sentinel) From e23cc089c261897cdaff6971ca1216dfc76223d1 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 19:04:26 +0200 Subject: [PATCH 22/25] address review comments --- Lib/multiprocessing/popen_fork.py | 2 ++ Lib/multiprocessing/popen_forkserver.py | 5 ++++- Lib/multiprocessing/popen_spawn_posix.py | 2 ++ Lib/multiprocessing/popen_spawn_win32.py | 2 +- Lib/multiprocessing/process.py | 10 +++++++--- Lib/test/_test_multiprocessing.py | 4 +--- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 4ec924e1f23aab..02551a9d1b4d46 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -79,7 +79,9 @@ def _launch(self, process_obj): os.close(child_w) os.close(child_r) self.finalizer = util.Finalize(self, os.close, (parent_r,)) + self.finalizer = util.Finalize(self, os.close, (parent_w,)) self.sentinel = parent_r + self._parent_w = parent_w def close(self): if self.finalizer is not None: diff --git a/Lib/multiprocessing/popen_forkserver.py b/Lib/multiprocessing/popen_forkserver.py index aba61a30b47030..c7c45473fa86d1 100644 --- a/Lib/multiprocessing/popen_forkserver.py +++ b/Lib/multiprocessing/popen_forkserver.py @@ -49,8 +49,11 @@ def _launch(self, process_obj): set_spawning_popen(None) self.sentinel, w = forkserver.connect_to_new_process(self._fds) + # Keep a duplicate of the data pipe's write end as a sentinel of the + # parent process used by the child process. + self._parent_w = os.dup(w) self.finalizer = util.Finalize(self, os.close, (self.sentinel,)) - parent_sentinel = os.dup(w) + self.finalizer = util.Finalize(self, os.close, (self._parent_w,)) with open(w, 'wb', closefd=True) as f: f.write(buf.getbuffer()) self.pid = forkserver.read_signed(self.sentinel) diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py index d8ce4cbe54031c..cbcd1c281bb7ca 100644 --- a/Lib/multiprocessing/popen_spawn_posix.py +++ b/Lib/multiprocessing/popen_spawn_posix.py @@ -63,6 +63,8 @@ def _launch(self, process_obj): finally: if parent_r is not None: self.finalizer = util.Finalize(self, os.close, (parent_r,)) + if parent_w is not None: + self.finalizer = util.Finalize(self, os.close, (parent_w,)) for fd in (child_r, child_w): if fd is not None: os.close(fd) diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index f7e0bdcac3ca3b..de4c5ecf1fa083 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -68,7 +68,7 @@ def __init__(self, process_obj): else: env = None - with open(wfd, 'wb', closefd=False) as to_child: + with open(wfd, 'wb', closefd=True) as to_child: # start process try: hp, ht, pid, tid = _winapi.CreateProcess( diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 0364ea140abe3f..5fa3bdf526e6aa 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -365,9 +365,6 @@ def __init__(self, name, pid, sentinel): self._sentinel = sentinel self._config = {} - def close(self): - pass - def is_alive(self): from multiprocessing.connection import wait return not wait([self._sentinel], timeout=0) @@ -376,6 +373,13 @@ def is_alive(self): 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=5) + pid = ident # diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index c70c93b2b443b7..ac4e7f518e0af8 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -326,9 +326,7 @@ def _test_report_parent_status(cls, wconn): wconn.send("alive" if parent_process().is_alive() else "not alive") start_time = time.monotonic() - while (parent_process().is_alive() and - (time.monotonic() - start_time) < 5): - time.sleep(0.1) + parent_process().join(timeout=5) wconn.send("alive" if parent_process().is_alive() else "not alive") From 7293d4cdb3e30255ecca035687854baea2b63278 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 19:59:04 +0200 Subject: [PATCH 23/25] FIX pass timeout correctly --- Lib/multiprocessing/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py index 5fa3bdf526e6aa..c62c826cff9580 100644 --- a/Lib/multiprocessing/process.py +++ b/Lib/multiprocessing/process.py @@ -378,7 +378,7 @@ def join(self, timeout=None): Wait until parent process terminates ''' from multiprocessing.connection import wait - wait([self._sentinel], timeout=5) + wait([self._sentinel], timeout=timeout) pid = ident From 80dcabf94e359080326899a836f5aa8f3bd2deb5 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 20:51:34 +0200 Subject: [PATCH 24/25] address review comments --- Lib/multiprocessing/popen_fork.py | 4 ++-- Lib/multiprocessing/popen_forkserver.py | 6 +++--- Lib/multiprocessing/popen_spawn_posix.py | 10 ++++++---- Lib/multiprocessing/util.py | 6 ++++++ Lib/test/_test_multiprocessing.py | 3 --- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 02551a9d1b4d46..36599d8be0bace 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -78,8 +78,8 @@ def _launch(self, process_obj): else: os.close(child_w) os.close(child_r) - self.finalizer = util.Finalize(self, os.close, (parent_r,)) - self.finalizer = util.Finalize(self, os.close, (parent_w,)) + self.finalizer = util.Finalize(self, util.close_fds, + (parent_r, parent_w,)) self.sentinel = parent_r self._parent_w = parent_w diff --git a/Lib/multiprocessing/popen_forkserver.py b/Lib/multiprocessing/popen_forkserver.py index c7c45473fa86d1..a56eb9bf11080b 100644 --- a/Lib/multiprocessing/popen_forkserver.py +++ b/Lib/multiprocessing/popen_forkserver.py @@ -51,9 +51,9 @@ def _launch(self, process_obj): self.sentinel, w = forkserver.connect_to_new_process(self._fds) # Keep a duplicate of the data pipe's write end as a sentinel of the # parent process used by the child process. - self._parent_w = os.dup(w) - self.finalizer = util.Finalize(self, os.close, (self.sentinel,)) - self.finalizer = util.Finalize(self, os.close, (self._parent_w,)) + _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) diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py index cbcd1c281bb7ca..24b8634523e5f2 100644 --- a/Lib/multiprocessing/popen_spawn_posix.py +++ b/Lib/multiprocessing/popen_spawn_posix.py @@ -61,10 +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,)) - if parent_w is not None: - self.finalizer = util.Finalize(self, os.close, (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): if fd is not None: os.close(fd) diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 0c4eb2473273b4..5674ad773f9764 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -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) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index ac4e7f518e0af8..ed5a9a18860e64 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -324,10 +324,7 @@ def _test_create_grandchild_process(cls, wconn): def _test_report_parent_status(cls, wconn): from multiprocessing.process import parent_process wconn.send("alive" if parent_process().is_alive() else "not alive") - - start_time = time.monotonic() parent_process().join(timeout=5) - wconn.send("alive" if parent_process().is_alive() else "not alive") def test_process(self): From 32aa64063b48ec32063a7b1e1554c947c9392819 Mon Sep 17 00:00:00 2001 From: Pierre Glaser Date: Mon, 20 May 2019 20:56:24 +0200 Subject: [PATCH 25/25] CLN unnecessary attribute --- Lib/multiprocessing/popen_fork.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 36599d8be0bace..11e216072d0919 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -81,7 +81,6 @@ def _launch(self, process_obj): self.finalizer = util.Finalize(self, util.close_fds, (parent_r, parent_w,)) self.sentinel = parent_r - self._parent_w = parent_w def close(self): if self.finalizer is not None: