Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Lib/multiprocessing/shared_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
38 changes: 38 additions & 0 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading