diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index 514152298b097c..10eb1f3f164bc2 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -159,6 +159,11 @@ def __init__(self, registry, address, authkey, serializer): self.id_to_local_proxy_obj = {} self.mutex = threading.Lock() + def _track_parent(self): + if process.parent_process() is not None: + process.parent_process().join() + self.stop_event.set() + def serve_forever(self): ''' Run the server forever @@ -169,6 +174,12 @@ def serve_forever(self): accepter = threading.Thread(target=self.accepter) accepter.daemon = True accepter.start() + + if process.parent_process() is not None: + watcher = threading.Thread(target=self._track_parent) + watcher.daemon = True + watcher.start() + try: while not self.stop_event.is_set(): self.stop_event.wait(1) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 071b54a713e2d8..39d220458215b7 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -3824,6 +3824,45 @@ def test_shared_memory_SharedMemoryServer_ignores_sigint(self): smm.shutdown() + def test_shared_memory_SharedMemoryManager_cleanup_if_parent_dies(self): + # If a process that spawned a SharedMemoryManager was terminated, its + # manager process should notice it, unlink the resources the manager + # delivered, and shut down. + cmd = '''if 1: + import sys, time + + from multiprocessing.managers import SharedMemoryManager + + + smm = SharedMemoryManager() + smm.start() + sl = smm.ShareableList([1, 2, 3]) + + sys.stdout.write(f'{sl.shm.name}\\n') + sys.stdout.flush() + time.sleep(100) + ''' + + p = subprocess.Popen([sys.executable, '-E', '-c', cmd], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + shm_name = p.stdout.readline().decode().strip() + p.terminate() + start_time = time.monotonic() + deadline = start_time + 60 + t = 0.1 + while time.monotonic() < deadline: + time.sleep(t) + # The shared_memory segment should be released by the + # SharedMemoryManager server process before it shuts down. + try: + sl = shared_memory.SharedMemory(shm_name, create=False) + except FileNotFoundError: + break + else: + raise AssertionError( + "A SharedMemoryManager prevented some resources to be cleaned " + "up after its parent process was terminated") + @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") def test_shared_memory_SharedMemoryManager_reuses_resource_tracker(self): # bpo-36867: test that a SharedMemoryManager uses the diff --git a/Misc/NEWS.d/next/Library/2019-05-20-20-56-20.bpo-36977.0Yf6-z.rst b/Misc/NEWS.d/next/Library/2019-05-20-20-56-20.bpo-36977.0Yf6-z.rst new file mode 100644 index 00000000000000..25c1be27ab7982 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-20-20-56-20.bpo-36977.0Yf6-z.rst @@ -0,0 +1 @@ +SharedMemoryManager server processes do not block anymore the release of shared memory segments if their parent process was unexpectedly terminated. \ No newline at end of file