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
+ """
2
6
3
7
4
8
__all__ = [ 'SharedMemory' , 'PosixSharedMemory' , 'WindowsNamedSharedMemory' ,
12
16
BarrierProxy , AcquirerProxy , ConditionProxy , EventProxy
13
17
from . import util
14
18
import os
15
- import random
16
19
import struct
17
20
import sys
18
21
import threading
22
+ import secrets
19
23
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
23
26
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
32
30
33
31
if os .name == "nt" :
34
32
import ctypes
@@ -89,15 +87,15 @@ class WindowsNamedSharedMemory:
89
87
90
88
def __init__ (self , name , flags = None , mode = 384 , size = 0 , read_only = False ):
91
89
if name is None :
92
- name = f'wnsm_ { os . getpid () } _ { random . randrange ( 100000 ) } '
90
+ name = _make_filename ()
93
91
94
92
if size == 0 :
95
93
# Attempt to dynamically determine the existing named shared
96
94
# memory block's size which is likely a multiple of mmap.PAGESIZE.
97
95
try :
98
96
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 )
101
99
try :
102
100
p_buf = kernel32 .MapViewOfFile (h_map , FILE_MAP_READ , 0 , 0 , 0 )
103
101
finally :
@@ -115,57 +113,146 @@ def __init__(self, name, flags=None, mode=384, size=0, read_only=False):
115
113
except OSError as ose :
116
114
name_collision = False
117
115
if name_collision :
118
- raise ExistentialError (
119
- f"Shared memory already exists with name={ name } "
120
- )
116
+ raise FileExistsError (name )
121
117
122
118
self ._mmap = mmap .mmap (- 1 , size , tagname = name )
123
- self .buf = memoryview (self ._mmap )
119
+ self ._buf = memoryview (self ._mmap )
124
120
self .name = name
125
121
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
127
133
128
134
def __repr__ (self ):
129
135
return f'{ self .__class__ .__name__ } ({ self .name !r} , size={ self .size } )'
130
136
131
137
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
134
144
135
145
def unlink (self ):
136
146
"""Windows ensures that destruction of the last reference to this
137
147
named shared memory block will result in the release of this memory."""
138
148
pass
139
149
140
150
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
142
179
143
180
def __init__ (self , name , flags = None , mode = 384 , size = 0 , read_only = False ):
144
181
if name and (flags is None ):
145
- _PosixSharedMemory . __init__ ( self , name , mode = mode )
182
+ flags = 0
146
183
else :
147
- if name is None :
148
- name = f'psm_{ os .getpid ()} _{ random .randrange (100000 )} '
149
184
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
159
205
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
161
232
162
233
def __repr__ (self ):
163
234
return f'{ self .__class__ .__name__ } ({ self .name !r} , size={ self .size } )'
164
235
236
+ def unlink (self ):
237
+ if self .name :
238
+ _posixshmem .shm_unlink (self .name )
239
+
165
240
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
169
256
170
257
171
258
class SharedMemory :
0 commit comments