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' ,
1216 BarrierProxy , AcquirerProxy , ConditionProxy , EventProxy
1317from . import util
1418import os
15- import random
1619import struct
1720import sys
1821import threading
22+ import secrets
1923try :
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
2326except 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
3331if 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
171258class SharedMemory :
0 commit comments