Skip to content

Commit 8cf9ba3

Browse files
committed
Merge branch 'enh-tests-neilsimplify-shmem' into enh-tests-shmem
adopting Neil's good work centralizing name autogeneration and removing custom exceptions.
2 parents 34f1e9a + 69dd8a9 commit 8cf9ba3

File tree

4 files changed

+299
-710
lines changed

4 files changed

+299
-710
lines changed

Lib/multiprocessing/shared_memory.py

Lines changed: 127 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
"Provides shared memory for direct access across processes."
1+
"""Provides shared memory for direct access across processes.
2+
3+
The API of this package is currently provisional. Refer to the
4+
documentation for details.
5+
"""
26

37

48
__all__ = [ 'SharedMemory', 'PosixSharedMemory', 'WindowsNamedSharedMemory',
@@ -12,23 +16,17 @@
1216
BarrierProxy, AcquirerProxy, ConditionProxy, EventProxy
1317
from . import util
1418
import os
15-
import random
1619
import struct
1720
import sys
1821
import threading
22+
import secrets
1923
try:
20-
from _posixshmem import _PosixSharedMemory, \
21-
Error, ExistentialError, PermissionsError, \
22-
O_CREAT, O_EXCL, O_CREX, O_TRUNC
24+
import _posixshmem
25+
from os import O_CREAT, O_EXCL, O_TRUNC
2326
except ImportError as ie:
24-
if os.name != "nt":
25-
# On Windows, posixshmem is not required to be available.
26-
raise ie
27-
else:
28-
_PosixSharedMemory = object
29-
class Error(Exception): pass
30-
class ExistentialError(Error): pass
31-
O_CREAT, O_EXCL, O_CREX, O_TRUNC = 64, 128, 192, 512
27+
O_CREAT, O_EXCL, O_TRUNC = 64, 128, 512
28+
29+
O_CREX = O_CREAT | O_EXCL
3230

3331
if os.name == "nt":
3432
import ctypes
@@ -89,15 +87,15 @@ class WindowsNamedSharedMemory:
8987

9088
def __init__(self, name, flags=None, mode=384, size=0, read_only=False):
9189
if name is None:
92-
name = f'wnsm_{os.getpid()}_{random.randrange(100000)}'
90+
name = _make_filename()
9391

9492
if size == 0:
9593
# Attempt to dynamically determine the existing named shared
9694
# memory block's size which is likely a multiple of mmap.PAGESIZE.
9795
try:
9896
h_map = kernel32.OpenFileMappingW(FILE_MAP_READ, False, name)
99-
except OSError as ose:
100-
raise ExistentialError(*ose.args)
97+
except OSError:
98+
raise FileNotFoundError(name)
10199
try:
102100
p_buf = kernel32.MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0)
103101
finally:
@@ -115,57 +113,146 @@ def __init__(self, name, flags=None, mode=384, size=0, read_only=False):
115113
except OSError as ose:
116114
name_collision = False
117115
if name_collision:
118-
raise ExistentialError(
119-
f"Shared memory already exists with name={name}"
120-
)
116+
raise FileExistsError(name)
121117

122118
self._mmap = mmap.mmap(-1, size, tagname=name)
123-
self.buf = memoryview(self._mmap)
119+
self._buf = memoryview(self._mmap)
124120
self.name = name
125121
self.mode = mode
126-
self.size = size
122+
self._size = size
123+
124+
@property
125+
def size(self):
126+
"Size in bytes."
127+
return self._size
128+
129+
@property
130+
def buf(self):
131+
"A memoryview of contents of the shared memory block."
132+
return self._buf
127133

128134
def __repr__(self):
129135
return f'{self.__class__.__name__}({self.name!r}, size={self.size})'
130136

131137
def close(self):
132-
self.buf.release()
133-
self._mmap.close()
138+
if self._buf is not None:
139+
self._buf.release()
140+
self._buf = None
141+
if self._mmap is not None:
142+
self._mmap.close()
143+
self._mmap = None
134144

135145
def unlink(self):
136146
"""Windows ensures that destruction of the last reference to this
137147
named shared memory block will result in the release of this memory."""
138148
pass
139149

140150

141-
class PosixSharedMemory(_PosixSharedMemory):
151+
# FreeBSD (and perhaps other BSDs) limit names to 14 characters.
152+
_SHM_SAFE_NAME_LENGTH = 14
153+
154+
# shared object name prefix
155+
if os.name == "nt":
156+
_SHM_NAME_PREFIX = 'wnsm_'
157+
else:
158+
_SHM_NAME_PREFIX = '/psm_'
159+
160+
161+
def _make_filename():
162+
"""Create a random filename for the shared memory object.
163+
"""
164+
# number of random bytes to use for name
165+
nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2
166+
assert nbytes >= 2, '_SHM_NAME_PREFIX too long'
167+
name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes)
168+
assert len(name) <= _SHM_SAFE_NAME_LENGTH
169+
return name
170+
171+
172+
class PosixSharedMemory:
173+
174+
# defaults so close() and unlink() can run without errors
175+
fd = -1
176+
name = None
177+
_mmap = None
178+
_buf = None
142179

143180
def __init__(self, name, flags=None, mode=384, size=0, read_only=False):
144181
if name and (flags is None):
145-
_PosixSharedMemory.__init__(self, name, mode=mode)
182+
flags = 0
146183
else:
147-
if name is None:
148-
name = f'psm_{os.getpid()}_{random.randrange(100000)}'
149184
flags = O_CREX if flags is None else flags
150-
_PosixSharedMemory.__init__(
151-
self,
152-
name,
153-
flags=flags,
154-
mode=mode,
155-
size=size,
156-
read_only=read_only
157-
)
158-
185+
if flags & O_EXCL and not flags & O_CREAT:
186+
raise ValueError("O_EXCL must be combined with O_CREAT")
187+
if name is None and not flags & O_EXCL:
188+
raise ValueError("'name' can only be None if O_EXCL is set")
189+
flags |= os.O_RDONLY if read_only else os.O_RDWR
190+
self.flags = flags
191+
self.mode = mode
192+
if not size >= 0:
193+
raise ValueError("'size' must be a positive integer")
194+
if name is None:
195+
self._open_retry()
196+
else:
197+
self.name = name
198+
self.fd = _posixshmem.shm_open(name, flags, mode=mode)
199+
if size:
200+
try:
201+
os.ftruncate(self.fd, size)
202+
except OSError:
203+
self.unlink()
204+
raise
159205
self._mmap = mmap.mmap(self.fd, self.size)
160-
self.buf = memoryview(self._mmap)
206+
self._buf = memoryview(self._mmap)
207+
208+
@property
209+
def size(self):
210+
"Size in bytes."
211+
if self.fd >= 0:
212+
return os.fstat(self.fd).st_size
213+
else:
214+
return 0
215+
216+
@property
217+
def buf(self):
218+
"A memoryview of contents of the shared memory block."
219+
return self._buf
220+
221+
def _open_retry(self):
222+
# generate a random name, open, retry if it exists
223+
while True:
224+
name = _make_filename()
225+
try:
226+
self.fd = _posixshmem.shm_open(name, self.flags,
227+
mode=self.mode)
228+
except FileExistsError:
229+
continue
230+
self.name = name
231+
break
161232

162233
def __repr__(self):
163234
return f'{self.__class__.__name__}({self.name!r}, size={self.size})'
164235

236+
def unlink(self):
237+
if self.name:
238+
_posixshmem.shm_unlink(self.name)
239+
165240
def close(self):
166-
self.buf.release()
167-
self._mmap.close()
168-
self.close_fd()
241+
if self._buf is not None:
242+
self._buf.release()
243+
self._buf = None
244+
if self._mmap is not None:
245+
self._mmap.close()
246+
self._mmap = None
247+
if self.fd >= 0:
248+
os.close(self.fd)
249+
self.fd = -1
250+
251+
def __del__(self):
252+
try:
253+
self.close()
254+
except OSError:
255+
pass
169256

170257

171258
class SharedMemory:

Lib/test/_test_multiprocessing.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3658,7 +3658,7 @@ def test_shared_memory_basics(self):
36583658
# manages unlinking on its own and unlink() does nothing).
36593659
# True release of shared memory segment does not necessarily
36603660
# happen until process exits, depending on the OS platform.
3661-
with self.assertRaises(shared_memory.ExistentialError):
3661+
with self.assertRaises(FileNotFoundError):
36623662
sms_uno = shared_memory.SharedMemory(
36633663
'test01_dblunlink',
36643664
flags=shared_memory.O_CREX,
@@ -3679,7 +3679,7 @@ def test_shared_memory_basics(self):
36793679
# Enforcement of `mode` and `read_only` is OS platform dependent
36803680
# and as such will not be tested here.
36813681

3682-
with self.assertRaises(shared_memory.ExistentialError):
3682+
with self.assertRaises(FileExistsError):
36833683
# Attempting to create a new shared memory segment with a
36843684
# name that is already in use triggers an exception.
36853685
there_can_only_be_one_sms = shared_memory.SharedMemory(
@@ -3704,7 +3704,7 @@ def test_shared_memory_basics(self):
37043704

37053705
# Attempting to attach to an existing shared memory segment when
37063706
# no segment exists with the supplied name triggers an exception.
3707-
with self.assertRaises(shared_memory.ExistentialError):
3707+
with self.assertRaises(FileNotFoundError):
37083708
nonexisting_sms = shared_memory.SharedMemory('test01_notthere')
37093709
nonexisting_sms.unlink() # Error should occur on prior line.
37103710

@@ -3745,7 +3745,7 @@ def test_shared_memory_SharedMemoryManager_basics(self):
37453745
if sys.platform != "win32":
37463746
# Calls to unlink() have no effect on Windows platform; shared
37473747
# memory will only be released once final process exits.
3748-
with self.assertRaises(shared_memory.ExistentialError):
3748+
with self.assertRaises(FileNotFoundError):
37493749
# No longer there to be attached to again.
37503750
absent_shm = shared_memory.SharedMemory(name=held_name)
37513751

@@ -3756,7 +3756,7 @@ def test_shared_memory_SharedMemoryManager_basics(self):
37563756
shm = smm2.SharedMemory(size=128)
37573757
held_name = sl.shm.name
37583758
if sys.platform != "win32":
3759-
with self.assertRaises(shared_memory.ExistentialError):
3759+
with self.assertRaises(FileNotFoundError):
37603760
# No longer there to be attached to again.
37613761
absent_sl = shared_memory.ShareableList(name=held_name)
37623762

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*[clinic input]
2+
preserve
3+
[clinic start generated code]*/
4+
5+
#if defined(HAVE_SHM_OPEN)
6+
7+
PyDoc_STRVAR(_posixshmem_shm_open__doc__,
8+
"shm_open($module, /, path, flags, mode=511)\n"
9+
"--\n"
10+
"\n"
11+
"Open a shared memory object. Returns a file descriptor (integer).");
12+
13+
#define _POSIXSHMEM_SHM_OPEN_METHODDEF \
14+
{"shm_open", (PyCFunction)(void(*)(void))_posixshmem_shm_open, METH_FASTCALL|METH_KEYWORDS, _posixshmem_shm_open__doc__},
15+
16+
static int
17+
_posixshmem_shm_open_impl(PyObject *module, PyObject *path, int flags,
18+
int mode);
19+
20+
static PyObject *
21+
_posixshmem_shm_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
22+
{
23+
PyObject *return_value = NULL;
24+
static const char * const _keywords[] = {"path", "flags", "mode", NULL};
25+
static _PyArg_Parser _parser = {"Ui|i:shm_open", _keywords, 0};
26+
PyObject *path;
27+
int flags;
28+
int mode = 511;
29+
int _return_value;
30+
31+
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
32+
&path, &flags, &mode)) {
33+
goto exit;
34+
}
35+
_return_value = _posixshmem_shm_open_impl(module, path, flags, mode);
36+
if ((_return_value == -1) && PyErr_Occurred()) {
37+
goto exit;
38+
}
39+
return_value = PyLong_FromLong((long)_return_value);
40+
41+
exit:
42+
return return_value;
43+
}
44+
45+
#endif /* defined(HAVE_SHM_OPEN) */
46+
47+
#if defined(HAVE_SHM_UNLINK)
48+
49+
PyDoc_STRVAR(_posixshmem_shm_unlink__doc__,
50+
"shm_unlink($module, /, path)\n"
51+
"--\n"
52+
"\n"
53+
"Remove a shared memory object (similar to unlink()).\n"
54+
"\n"
55+
"Remove a shared memory object name, and, once all processes have unmapped\n"
56+
"the object, de-allocates and destroys the contents of the associated memory\n"
57+
"region.");
58+
59+
#define _POSIXSHMEM_SHM_UNLINK_METHODDEF \
60+
{"shm_unlink", (PyCFunction)(void(*)(void))_posixshmem_shm_unlink, METH_FASTCALL|METH_KEYWORDS, _posixshmem_shm_unlink__doc__},
61+
62+
static PyObject *
63+
_posixshmem_shm_unlink_impl(PyObject *module, PyObject *path);
64+
65+
static PyObject *
66+
_posixshmem_shm_unlink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
67+
{
68+
PyObject *return_value = NULL;
69+
static const char * const _keywords[] = {"path", NULL};
70+
static _PyArg_Parser _parser = {"U:shm_unlink", _keywords, 0};
71+
PyObject *path;
72+
73+
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
74+
&path)) {
75+
goto exit;
76+
}
77+
return_value = _posixshmem_shm_unlink_impl(module, path);
78+
79+
exit:
80+
return return_value;
81+
}
82+
83+
#endif /* defined(HAVE_SHM_UNLINK) */
84+
85+
#ifndef _POSIXSHMEM_SHM_OPEN_METHODDEF
86+
#define _POSIXSHMEM_SHM_OPEN_METHODDEF
87+
#endif /* !defined(_POSIXSHMEM_SHM_OPEN_METHODDEF) */
88+
89+
#ifndef _POSIXSHMEM_SHM_UNLINK_METHODDEF
90+
#define _POSIXSHMEM_SHM_UNLINK_METHODDEF
91+
#endif /* !defined(_POSIXSHMEM_SHM_UNLINK_METHODDEF) */
92+
/*[clinic end generated code: output=ff9cf0bc9b8baddf input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)