From 759ae67746f205397a855d89366b2f8b93065840 Mon Sep 17 00:00:00 2001 From: Ugur Levent Akaoglu Date: Tue, 7 Jul 2026 19:30:47 +0300 Subject: [PATCH] gh-153279: Don't unlink another process's shared memory block on failed attach SharedMemory.__init__ called self.unlink() from its OSError cleanup handler unconditionally. On the attach path (create=False) this destroyed a block owned by another process when mmap() failed (e.g. ENOMEM). The cleanup now only closes the file descriptor and unlinks the block only when it was created in the failing call. Because the block has not been registered with the resource tracker yet at that point, it is unlinked directly to avoid a spurious UNREGISTER (which raised KeyError in the resource tracker on the create path). --- Lib/multiprocessing/shared_memory.py | 9 ++++- Lib/test/_test_multiprocessing.py | 38 +++++++++++++++++++ ...-07-07-19-30-33.gh-issue-153279.xxY4MA.rst | 6 +++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-07-19-30-33.gh-issue-153279.xxY4MA.rst diff --git a/Lib/multiprocessing/shared_memory.py b/Lib/multiprocessing/shared_memory.py index 99a8ce3320ad4ee..e34434c95689094 100644 --- a/Lib/multiprocessing/shared_memory.py +++ b/Lib/multiprocessing/shared_memory.py @@ -116,7 +116,14 @@ def __init__(self, name=None, create=False, size=0, *, track=True): size = stats.st_size self._mmap = mmap.mmap(self._fd, size) except OSError: - self.unlink() + self.close() + # Only destroy the shared memory block if we created it in + # this call. When attaching to a block created by another + # process we must not unlink another owner's memory. The + # block has not been registered with the resource tracker + # yet, so unlink it directly to avoid a spurious UNREGISTER. + if create: + _posixshmem.shm_unlink(self._name) raise if self._track: resource_tracker.register(self._name, "shared_memory") diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 115a187a8a85882..cf887d4130e10ba 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -4615,6 +4615,44 @@ class OptionalAttachSharedMemory(shared_memory.SharedMemory): sms.close() + @unittest.skipUnless(shared_memory._USE_POSIX, + "POSIX shared memory required") + def test_shared_memory_attach_failure_preserves_block(self): + # A failure to mmap while *attaching* to an existing shared memory + # block (create=False) must not unlink (destroy) the block, which + # is owned by another party. + name = self._new_shm_name('test_attach_fail') + owner = shared_memory.SharedMemory(name, create=True, size=1024) + self.addCleanup(owner.unlink) + self.addCleanup(owner.close) + + with unittest.mock.patch( + 'multiprocessing.shared_memory.mmap.mmap', + side_effect=OSError("simulated mmap failure")): + with self.assertRaises(OSError): + shared_memory.SharedMemory(name) # attach must not unlink + + # The owner's block must still be attachable. + attached = shared_memory.SharedMemory(name) + self.addCleanup(attached.close) + self.assertGreaterEqual(attached.size, 1024) + + @unittest.skipUnless(shared_memory._USE_POSIX, + "POSIX shared memory required") + def test_shared_memory_create_failure_cleans_up(self): + # A failure to mmap while *creating* a block must destroy the + # just-created block so that it is not leaked. + name = self._new_shm_name('test_create_fail') + with unittest.mock.patch( + 'multiprocessing.shared_memory.mmap.mmap', + side_effect=OSError("simulated mmap failure")): + with self.assertRaises(OSError): + shared_memory.SharedMemory(name, create=True, size=1024) + + # The half-created block must not survive. + with self.assertRaises(FileNotFoundError): + shared_memory.SharedMemory(name) + def test_shared_memory_recreate(self): # Test if shared memory segment is created properly, # when _make_filename returns an existing shared memory segment name diff --git a/Misc/NEWS.d/next/Library/2026-07-07-19-30-33.gh-issue-153279.xxY4MA.rst b/Misc/NEWS.d/next/Library/2026-07-07-19-30-33.gh-issue-153279.xxY4MA.rst new file mode 100644 index 000000000000000..e4c3924b05b201e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-07-19-30-33.gh-issue-153279.xxY4MA.rst @@ -0,0 +1,6 @@ +Fix :class:`multiprocessing.shared_memory.SharedMemory` destroying another +process's shared memory block when attaching to it fails. A failure to +:func:`~mmap.mmap` while attaching (``create=False``) no longer unlinks the +block, which is owned by another process; the file descriptor is simply +closed. The block is now unlinked only when it was created in the failing +call.