Skip to content

Commit 5835415

Browse files
committed
Allow zero-length shared memory objects
These are allowed by POSIX and it seems more consistent/cleaner to support them than forcing the user to treat them as a special case. Windows does not support creating a zero-length mapping, and neither does `mmap` on any platform, so skip those operations and assign a zero-length buffer instead. TODO: The way the POSIX/Windows code is interleaved inside if statements makes for difficult to follow and deeply nested logic. It might be better to refactor this code and keep the platform-specific logic in separate functions or possibly even implementation classes.
1 parent e33f341 commit 5835415

File tree

2 files changed

+20
-32
lines changed

2 files changed

+20
-32
lines changed

Lib/multiprocessing/shared_memory.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,10 @@ class SharedMemory:
7474
_track = True
7575

7676
def __init__(self, name=None, create=False, size=0, *, track=True):
77-
if not size >= 0:
78-
raise ValueError("'size' must be a positive integer")
77+
if size < 0:
78+
raise ValueError("'size' must be a non-negative integer")
7979
if create:
8080
self._flags = _O_CREX | os.O_RDWR
81-
if size == 0:
82-
raise ValueError("'size' must be a positive number different from zero")
8381
if name is None and not self._flags & os.O_EXCL:
8482
raise ValueError("'name' can only be None if create=True")
8583

@@ -114,7 +112,8 @@ def __init__(self, name=None, create=False, size=0, *, track=True):
114112
os.ftruncate(self._fd, size)
115113
stats = os.fstat(self._fd)
116114
size = stats.st_size
117-
self._mmap = mmap.mmap(self._fd, size)
115+
if size > 0:
116+
self._mmap = mmap.mmap(self._fd, size)
118117
except:
119118
if create:
120119
_posixshmem.shm_unlink(self._name)
@@ -126,7 +125,7 @@ def __init__(self, name=None, create=False, size=0, *, track=True):
126125

127126
# Windows Named Shared Memory
128127

129-
if create:
128+
if create and size > 0:
130129
while True:
131130
temp_name = _make_filename() if name is None else name
132131
# Create and reserve shared memory block with this name
@@ -156,7 +155,9 @@ def __init__(self, name=None, create=False, size=0, *, track=True):
156155
_winapi.CloseHandle(h_map)
157156
self._name = temp_name
158157
break
159-
158+
elif create and size == 0:
159+
# TODO: Leave as None?
160+
self._name = _make_filename() if name is None else name
160161
else:
161162
self._name = name
162163
# Dynamically determine the existing named shared memory
@@ -180,10 +181,14 @@ def __init__(self, name=None, create=False, size=0, *, track=True):
180181
size = _winapi.VirtualQuerySize(p_buf)
181182
finally:
182183
_winapi.UnmapViewOfFile(p_buf)
183-
self._mmap = mmap.mmap(-1, size, tagname=name)
184+
if size > 0:
185+
self._mmap = mmap.mmap(-1, size, tagname=name)
184186

185187
self._size = size
186-
self._buf = memoryview(self._mmap)
188+
if size > 0:
189+
self._buf = memoryview(self._mmap)
190+
else:
191+
self._buf = memoryview(b'')
187192

188193
def __del__(self):
189194
try:

Lib/test/_test_multiprocessing.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4447,14 +4447,6 @@ def test_invalid_shared_memory_creation(self):
44474447
with self.assertRaises(ValueError):
44484448
sms_invalid = shared_memory.SharedMemory(create=True, size=-1)
44494449

4450-
# Test creating a shared memory segment with size 0
4451-
with self.assertRaises(ValueError):
4452-
sms_invalid = shared_memory.SharedMemory(create=True, size=0)
4453-
4454-
# Test creating a shared memory segment without size argument
4455-
with self.assertRaises(ValueError):
4456-
sms_invalid = shared_memory.SharedMemory(create=True)
4457-
44584450
def test_shared_memory_pickle_unpickle(self):
44594451
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
44604452
with self.subTest(proto=proto):
@@ -4871,21 +4863,12 @@ def test_existing_shm_not_unlinked_on_error(self):
48714863
with self.assertRaises(OSError):
48724864
shared_memory.SharedMemory(name, create=False)
48734865

4874-
@unittest.skipIf(os.name != "posix", "posix-only test w/ empty file")
4875-
def test_cleanup_zero_length_shared_memory(self):
4876-
import _posixshmem
4877-
4878-
name = self._new_shm_name("test_init_cleanup")
4879-
mem = _posixshmem.shm_open(name, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o600)
4880-
os.close(mem)
4881-
4882-
# First time the mmap.mmap fails with a ValueError, as the shared
4883-
# memory is zero-length and hence the mmap fails...
4884-
with self.assertRaises(ValueError):
4885-
shared_memory.SharedMemory(name, create=False)
4886-
4887-
# ...but it should NOT delete the shared memory as part of its cleanup
4888-
_posixshmem.shm_unlink(name)
4866+
def test_zero_length_shared_memory(self):
4867+
name = self._new_shm_name("test_zero_length_shared_memory")
4868+
mem = shared_memory.SharedMemory(name, create=True, size=0)
4869+
self.addCleanup(mem.unlink)
4870+
self.assertEqual(mem.size, 0)
4871+
self.assertEqual(len(mem.buf), 0)
48894872

48904873
#
48914874
# Test to verify that `Finalize` works.

0 commit comments

Comments
 (0)