| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | """Provides shared memory for direct access across processes.
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | The API of this package is currently provisional. Refer to the | 
					
						
							|  |  |  | documentation for details. | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | __all__ = [ 'SharedMemory', 'ShareableList' ] | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | from functools import partial | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | import mmap | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | import errno | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | import struct | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | import secrets | 
					
						
							| 
									
										
										
										
											2020-04-10 17:46:36 +03:00
										 |  |  | import types | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | if os.name == "nt": | 
					
						
							|  |  |  |     import _winapi | 
					
						
							|  |  |  |     _USE_POSIX = False | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     import _posixshmem | 
					
						
							|  |  |  |     _USE_POSIX = True | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-16 14:41:51 +01:00
										 |  |  | from . import resource_tracker | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | _O_CREX = os.O_CREAT | os.O_EXCL | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | # FreeBSD (and perhaps other BSDs) limit names to 14 characters. | 
					
						
							|  |  |  | _SHM_SAFE_NAME_LENGTH = 14 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | # Shared memory block name prefix | 
					
						
							|  |  |  | if _USE_POSIX: | 
					
						
							| 
									
										
										
										
											2019-02-25 16:41:52 -06:00
										 |  |  |     _SHM_NAME_PREFIX = '/psm_' | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | else: | 
					
						
							|  |  |  |     _SHM_NAME_PREFIX = 'wnsm_' | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | def _make_filename(): | 
					
						
							|  |  |  |     "Create a random filename for the shared memory object." | 
					
						
							|  |  |  |     # number of random bytes to use for name | 
					
						
							|  |  |  |     nbytes = (_SHM_SAFE_NAME_LENGTH - len(_SHM_NAME_PREFIX)) // 2 | 
					
						
							|  |  |  |     assert nbytes >= 2, '_SHM_NAME_PREFIX too long' | 
					
						
							|  |  |  |     name = _SHM_NAME_PREFIX + secrets.token_hex(nbytes) | 
					
						
							|  |  |  |     assert len(name) <= _SHM_SAFE_NAME_LENGTH | 
					
						
							|  |  |  |     return name | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SharedMemory: | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     """Creates a new shared memory block or attaches to an existing
 | 
					
						
							|  |  |  |     shared memory block. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Every shared memory block is assigned a unique name.  This enables | 
					
						
							|  |  |  |     one process to create a shared memory block with a particular name | 
					
						
							|  |  |  |     so that a different process can attach to that same shared memory | 
					
						
							|  |  |  |     block using that same name. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     As a resource for sharing data across processes, shared memory blocks | 
					
						
							|  |  |  |     may outlive the original process that created them.  When one process | 
					
						
							|  |  |  |     no longer needs access to a shared memory block that might still be | 
					
						
							|  |  |  |     needed by other processes, the close() method should be called. | 
					
						
							|  |  |  |     When a shared memory block is no longer needed by any process, the | 
					
						
							|  |  |  |     unlink() method should be called to ensure proper cleanup."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Defaults; enables close() and unlink() to run without errors. | 
					
						
							|  |  |  |     _name = None | 
					
						
							|  |  |  |     _fd = -1 | 
					
						
							|  |  |  |     _mmap = None | 
					
						
							|  |  |  |     _buf = None | 
					
						
							|  |  |  |     _flags = os.O_RDWR | 
					
						
							|  |  |  |     _mode = 0o600 | 
					
						
							| 
									
										
										
										
											2019-02-25 16:41:52 -06:00
										 |  |  |     _prepend_leading_slash = True if _USE_POSIX else False | 
					
						
							| 
									
										
										
										
											2023-12-05 09:11:44 +01:00
										 |  |  |     _track = True | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-05 09:11:44 +01:00
										 |  |  |     def __init__(self, name=None, create=False, size=0, *, track=True): | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         if not size >= 0: | 
					
						
							|  |  |  |             raise ValueError("'size' must be a positive integer") | 
					
						
							|  |  |  |         if create: | 
					
						
							|  |  |  |             self._flags = _O_CREX | os.O_RDWR | 
					
						
							| 
									
										
										
										
											2020-08-31 00:33:11 +05:30
										 |  |  |             if size == 0: | 
					
						
							|  |  |  |                 raise ValueError("'size' must be a positive number different from zero") | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         if name is None and not self._flags & os.O_EXCL: | 
					
						
							|  |  |  |             raise ValueError("'name' can only be None if create=True") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-05 09:11:44 +01:00
										 |  |  |         self._track = track | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         if _USE_POSIX: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # POSIX Shared Memory | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if name is None: | 
					
						
							|  |  |  |                 while True: | 
					
						
							|  |  |  |                     name = _make_filename() | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         self._fd = _posixshmem.shm_open( | 
					
						
							|  |  |  |                             name, | 
					
						
							|  |  |  |                             self._flags, | 
					
						
							|  |  |  |                             mode=self._mode | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                     except FileExistsError: | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  |                     self._name = name | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2019-02-25 16:41:52 -06:00
										 |  |  |                 name = "/" + name if self._prepend_leading_slash else name | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 self._fd = _posixshmem.shm_open( | 
					
						
							|  |  |  |                     name, | 
					
						
							|  |  |  |                     self._flags, | 
					
						
							|  |  |  |                     mode=self._mode | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 self._name = name | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 if create and size: | 
					
						
							|  |  |  |                     os.ftruncate(self._fd, size) | 
					
						
							|  |  |  |                 stats = os.fstat(self._fd) | 
					
						
							|  |  |  |                 size = stats.st_size | 
					
						
							|  |  |  |                 self._mmap = mmap.mmap(self._fd, size) | 
					
						
							|  |  |  |             except OSError: | 
					
						
							|  |  |  |                 self.unlink() | 
					
						
							|  |  |  |                 raise | 
					
						
							| 
									
										
										
										
											2023-12-05 09:11:44 +01:00
										 |  |  |             if self._track: | 
					
						
							|  |  |  |                 resource_tracker.register(self._name, "shared_memory") | 
					
						
							| 
									
										
										
										
											2019-05-10 22:59:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         else: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |             # Windows Named Shared Memory | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if create: | 
					
						
							|  |  |  |                 while True: | 
					
						
							|  |  |  |                     temp_name = _make_filename() if name is None else name | 
					
						
							|  |  |  |                     # Create and reserve shared memory block with this name | 
					
						
							|  |  |  |                     # until it can be attached to by mmap. | 
					
						
							|  |  |  |                     h_map = _winapi.CreateFileMapping( | 
					
						
							|  |  |  |                         _winapi.INVALID_HANDLE_VALUE, | 
					
						
							|  |  |  |                         _winapi.NULL, | 
					
						
							|  |  |  |                         _winapi.PAGE_READWRITE, | 
					
						
							|  |  |  |                         (size >> 32) & 0xFFFFFFFF, | 
					
						
							|  |  |  |                         size & 0xFFFFFFFF, | 
					
						
							|  |  |  |                         temp_name | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         last_error_code = _winapi.GetLastError() | 
					
						
							|  |  |  |                         if last_error_code == _winapi.ERROR_ALREADY_EXISTS: | 
					
						
							|  |  |  |                             if name is not None: | 
					
						
							|  |  |  |                                 raise FileExistsError( | 
					
						
							|  |  |  |                                     errno.EEXIST, | 
					
						
							|  |  |  |                                     os.strerror(errno.EEXIST), | 
					
						
							|  |  |  |                                     name, | 
					
						
							|  |  |  |                                     _winapi.ERROR_ALREADY_EXISTS | 
					
						
							|  |  |  |                                 ) | 
					
						
							|  |  |  |                             else: | 
					
						
							|  |  |  |                                 continue | 
					
						
							|  |  |  |                         self._mmap = mmap.mmap(-1, size, tagname=temp_name) | 
					
						
							|  |  |  |                     finally: | 
					
						
							|  |  |  |                         _winapi.CloseHandle(h_map) | 
					
						
							|  |  |  |                     self._name = temp_name | 
					
						
							|  |  |  |                     break | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 self._name = name | 
					
						
							|  |  |  |                 # Dynamically determine the existing named shared memory | 
					
						
							|  |  |  |                 # block's size which is likely a multiple of mmap.PAGESIZE. | 
					
						
							|  |  |  |                 h_map = _winapi.OpenFileMapping( | 
					
						
							|  |  |  |                     _winapi.FILE_MAP_READ, | 
					
						
							|  |  |  |                     False, | 
					
						
							|  |  |  |                     name | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     p_buf = _winapi.MapViewOfFile( | 
					
						
							|  |  |  |                         h_map, | 
					
						
							|  |  |  |                         _winapi.FILE_MAP_READ, | 
					
						
							|  |  |  |                         0, | 
					
						
							|  |  |  |                         0, | 
					
						
							|  |  |  |                         0 | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 finally: | 
					
						
							|  |  |  |                     _winapi.CloseHandle(h_map) | 
					
						
							| 
									
										
										
										
											2022-11-25 09:39:48 -08:00
										 |  |  |                 try: | 
					
						
							|  |  |  |                     size = _winapi.VirtualQuerySize(p_buf) | 
					
						
							|  |  |  |                 finally: | 
					
						
							|  |  |  |                     _winapi.UnmapViewOfFile(p_buf) | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 self._mmap = mmap.mmap(-1, size, tagname=name) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         self._size = size | 
					
						
							|  |  |  |         self._buf = memoryview(self._mmap) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def __del__(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             self.close() | 
					
						
							|  |  |  |         except OSError: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __reduce__(self): | 
					
						
							|  |  |  |         return ( | 
					
						
							|  |  |  |             self.__class__, | 
					
						
							|  |  |  |             ( | 
					
						
							|  |  |  |                 self.name, | 
					
						
							|  |  |  |                 False, | 
					
						
							|  |  |  |                 self.size, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return f'{self.__class__.__name__}({self.name!r}, size={self.size})' | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def buf(self): | 
					
						
							|  |  |  |         "A memoryview of contents of the shared memory block." | 
					
						
							|  |  |  |         return self._buf | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def name(self): | 
					
						
							|  |  |  |         "Unique name that identifies the shared memory block." | 
					
						
							| 
									
										
										
										
											2019-02-25 16:41:52 -06:00
										 |  |  |         reported_name = self._name | 
					
						
							|  |  |  |         if _USE_POSIX and self._prepend_leading_slash: | 
					
						
							|  |  |  |             if self._name.startswith("/"): | 
					
						
							|  |  |  |                 reported_name = self._name[1:] | 
					
						
							|  |  |  |         return reported_name | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def size(self): | 
					
						
							|  |  |  |         "Size in bytes." | 
					
						
							|  |  |  |         return self._size | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def close(self): | 
					
						
							|  |  |  |         """Closes access to the shared memory from this instance but does
 | 
					
						
							|  |  |  |         not destroy the shared memory block."""
 | 
					
						
							|  |  |  |         if self._buf is not None: | 
					
						
							|  |  |  |             self._buf.release() | 
					
						
							|  |  |  |             self._buf = None | 
					
						
							|  |  |  |         if self._mmap is not None: | 
					
						
							|  |  |  |             self._mmap.close() | 
					
						
							|  |  |  |             self._mmap = None | 
					
						
							|  |  |  |         if _USE_POSIX and self._fd >= 0: | 
					
						
							|  |  |  |             os.close(self._fd) | 
					
						
							|  |  |  |             self._fd = -1 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def unlink(self): | 
					
						
							|  |  |  |         """Requests that the underlying shared memory block be destroyed.
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-05 09:11:44 +01:00
										 |  |  |         Unlink should be called once (and only once) across all handles | 
					
						
							|  |  |  |         which have access to the shared memory block, even if these | 
					
						
							|  |  |  |         handles belong to different processes. Closing and unlinking may | 
					
						
							|  |  |  |         happen in any order, but trying to access data inside a shared | 
					
						
							|  |  |  |         memory block after unlinking may result in memory errors, | 
					
						
							|  |  |  |         depending on platform. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         This method has no effect on Windows, where the only way to | 
					
						
							|  |  |  |         delete a shared memory block is to close all handles."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 16:41:52 -06:00
										 |  |  |         if _USE_POSIX and self._name: | 
					
						
							|  |  |  |             _posixshmem.shm_unlink(self._name) | 
					
						
							| 
									
										
										
										
											2023-12-05 09:11:44 +01:00
										 |  |  |             if self._track: | 
					
						
							|  |  |  |                 resource_tracker.unregister(self._name, "shared_memory") | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  | _encoding = "utf8" | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ShareableList: | 
					
						
							|  |  |  |     """Pattern for a mutable list-like object shareable via a shared
 | 
					
						
							|  |  |  |     memory block.  It differs from the built-in list type in that these | 
					
						
							|  |  |  |     lists can not change their overall length (i.e. no append, insert, | 
					
						
							|  |  |  |     etc.) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Because values are packed into a memoryview as bytes, the struct | 
					
						
							|  |  |  |     packing format for any storable value must require no more than 8 | 
					
						
							|  |  |  |     characters to describe its format."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |     # The shared memory area is organized as follows: | 
					
						
							|  |  |  |     # - 8 bytes: number of items (N) as a 64-bit integer | 
					
						
							|  |  |  |     # - (N + 1) * 8 bytes: offsets of each element from the start of the | 
					
						
							|  |  |  |     #                      data area | 
					
						
							|  |  |  |     # - K bytes: the data area storing item values (with encoding and size | 
					
						
							|  |  |  |     #            depending on their respective types) | 
					
						
							|  |  |  |     # - N * 8 bytes: `struct` format string for each element | 
					
						
							|  |  |  |     # - N bytes: index into _back_transforms_mapping for each element | 
					
						
							|  |  |  |     #            (for reconstructing the corresponding Python value) | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     _types_mapping = { | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         int: "q", | 
					
						
							|  |  |  |         float: "d", | 
					
						
							|  |  |  |         bool: "xxxxxxx?", | 
					
						
							|  |  |  |         str: "%ds", | 
					
						
							|  |  |  |         bytes: "%ds", | 
					
						
							|  |  |  |         None.__class__: "xxxxxx?x", | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     _alignment = 8 | 
					
						
							|  |  |  |     _back_transforms_mapping = { | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         0: lambda value: value,                   # int, float, bool | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         1: lambda value: value.rstrip(b'\x00').decode(_encoding),  # str | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         2: lambda value: value.rstrip(b'\x00'),   # bytes | 
					
						
							|  |  |  |         3: lambda _value: None,                   # None | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							|  |  |  |     def _extract_recreation_code(value): | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         """Used in concert with _back_transforms_mapping to convert values
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         into the appropriate Python objects when retrieving them from | 
					
						
							|  |  |  |         the list as well as when storing them."""
 | 
					
						
							|  |  |  |         if not isinstance(value, (str, bytes, None.__class__)): | 
					
						
							|  |  |  |             return 0 | 
					
						
							|  |  |  |         elif isinstance(value, str): | 
					
						
							|  |  |  |             return 1 | 
					
						
							|  |  |  |         elif isinstance(value, bytes): | 
					
						
							|  |  |  |             return 2 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return 3  # NoneType | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def __init__(self, sequence=None, *, name=None): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         if name is None or sequence is not None: | 
					
						
							|  |  |  |             sequence = sequence or () | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             _formats = [ | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 self._types_mapping[type(item)] | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |                     if not isinstance(item, (str, bytes)) | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                     else self._types_mapping[type(item)] % ( | 
					
						
							|  |  |  |                         self._alignment * (len(item) // self._alignment + 1), | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 for item in sequence | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ] | 
					
						
							|  |  |  |             self._list_len = len(_formats) | 
					
						
							|  |  |  |             assert sum(len(fmt) <= 8 for fmt in _formats) == self._list_len | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |             offset = 0 | 
					
						
							|  |  |  |             # The offsets of each list element into the shared memory's | 
					
						
							|  |  |  |             # data area (0 meaning the start of the data area, not the start | 
					
						
							|  |  |  |             # of the shared memory area). | 
					
						
							|  |  |  |             self._allocated_offsets = [0] | 
					
						
							|  |  |  |             for fmt in _formats: | 
					
						
							|  |  |  |                 offset += self._alignment if fmt[-1] != "s" else int(fmt[:-1]) | 
					
						
							|  |  |  |                 self._allocated_offsets.append(offset) | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |             _recreation_codes = [ | 
					
						
							|  |  |  |                 self._extract_recreation_code(item) for item in sequence | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ] | 
					
						
							|  |  |  |             requested_size = struct.calcsize( | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 "q" + self._format_size_metainfo + | 
					
						
							|  |  |  |                 "".join(_formats) + | 
					
						
							|  |  |  |                 self._format_packing_metainfo + | 
					
						
							|  |  |  |                 self._format_back_transform_codes | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |             self.shm = SharedMemory(name, create=True, size=requested_size) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |             self.shm = SharedMemory(name) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         if sequence is not None: | 
					
						
							|  |  |  |             _enc = _encoding | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             struct.pack_into( | 
					
						
							|  |  |  |                 "q" + self._format_size_metainfo, | 
					
						
							|  |  |  |                 self.shm.buf, | 
					
						
							|  |  |  |                 0, | 
					
						
							|  |  |  |                 self._list_len, | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |                 *(self._allocated_offsets) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ) | 
					
						
							|  |  |  |             struct.pack_into( | 
					
						
							|  |  |  |                 "".join(_formats), | 
					
						
							|  |  |  |                 self.shm.buf, | 
					
						
							|  |  |  |                 self._offset_data_start, | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 *(v.encode(_enc) if isinstance(v, str) else v for v in sequence) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ) | 
					
						
							|  |  |  |             struct.pack_into( | 
					
						
							|  |  |  |                 self._format_packing_metainfo, | 
					
						
							|  |  |  |                 self.shm.buf, | 
					
						
							|  |  |  |                 self._offset_packing_formats, | 
					
						
							|  |  |  |                 *(v.encode(_enc) for v in _formats) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             struct.pack_into( | 
					
						
							|  |  |  |                 self._format_back_transform_codes, | 
					
						
							|  |  |  |                 self.shm.buf, | 
					
						
							|  |  |  |                 self._offset_back_transform_codes, | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 *(_recreation_codes) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self._list_len = len(self)  # Obtains size from offset 0 in buffer. | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |             self._allocated_offsets = list( | 
					
						
							|  |  |  |                 struct.unpack_from( | 
					
						
							|  |  |  |                     self._format_size_metainfo, | 
					
						
							|  |  |  |                     self.shm.buf, | 
					
						
							|  |  |  |                     1 * 8 | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _get_packing_format(self, position): | 
					
						
							|  |  |  |         "Gets the packing format for a single value stored in the list." | 
					
						
							|  |  |  |         position = position if position >= 0 else position + self._list_len | 
					
						
							|  |  |  |         if (position >= self._list_len) or (self._list_len < 0): | 
					
						
							|  |  |  |             raise IndexError("Requested position out of range.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         v = struct.unpack_from( | 
					
						
							|  |  |  |             "8s", | 
					
						
							|  |  |  |             self.shm.buf, | 
					
						
							|  |  |  |             self._offset_packing_formats + position * 8 | 
					
						
							|  |  |  |         )[0] | 
					
						
							|  |  |  |         fmt = v.rstrip(b'\x00') | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         fmt_as_str = fmt.decode(_encoding) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return fmt_as_str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _get_back_transform(self, position): | 
					
						
							|  |  |  |         "Gets the back transformation function for a single value." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (position >= self._list_len) or (self._list_len < 0): | 
					
						
							|  |  |  |             raise IndexError("Requested position out of range.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         transform_code = struct.unpack_from( | 
					
						
							|  |  |  |             "b", | 
					
						
							|  |  |  |             self.shm.buf, | 
					
						
							|  |  |  |             self._offset_back_transform_codes + position | 
					
						
							|  |  |  |         )[0] | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         transform_function = self._back_transforms_mapping[transform_code] | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return transform_function | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _set_packing_format_and_transform(self, position, fmt_as_str, value): | 
					
						
							|  |  |  |         """Sets the packing format and back transformation code for a
 | 
					
						
							|  |  |  |         single value in the list at the specified position."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (position >= self._list_len) or (self._list_len < 0): | 
					
						
							|  |  |  |             raise IndexError("Requested position out of range.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         struct.pack_into( | 
					
						
							|  |  |  |             "8s", | 
					
						
							|  |  |  |             self.shm.buf, | 
					
						
							|  |  |  |             self._offset_packing_formats + position * 8, | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |             fmt_as_str.encode(_encoding) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         transform_code = self._extract_recreation_code(value) | 
					
						
							|  |  |  |         struct.pack_into( | 
					
						
							|  |  |  |             "b", | 
					
						
							|  |  |  |             self.shm.buf, | 
					
						
							|  |  |  |             self._offset_back_transform_codes + position, | 
					
						
							|  |  |  |             transform_code | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __getitem__(self, position): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         position = position if position >= 0 else position + self._list_len | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |             offset = self._offset_data_start + self._allocated_offsets[position] | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             (v,) = struct.unpack_from( | 
					
						
							|  |  |  |                 self._get_packing_format(position), | 
					
						
							|  |  |  |                 self.shm.buf, | 
					
						
							|  |  |  |                 offset | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         except IndexError: | 
					
						
							|  |  |  |             raise IndexError("index out of range") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         back_transform = self._get_back_transform(position) | 
					
						
							|  |  |  |         v = back_transform(v) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return v | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __setitem__(self, position, value): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         position = position if position >= 0 else position + self._list_len | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |             item_offset = self._allocated_offsets[position] | 
					
						
							|  |  |  |             offset = self._offset_data_start + item_offset | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             current_format = self._get_packing_format(position) | 
					
						
							|  |  |  |         except IndexError: | 
					
						
							|  |  |  |             raise IndexError("assignment index out of range") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not isinstance(value, (str, bytes)): | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |             new_format = self._types_mapping[type(value)] | 
					
						
							| 
									
										
										
										
											2020-04-20 20:54:55 +02:00
										 |  |  |             encoded_value = value | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |             allocated_length = self._allocated_offsets[position + 1] - item_offset | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 20:54:55 +02:00
										 |  |  |             encoded_value = (value.encode(_encoding) | 
					
						
							|  |  |  |                              if isinstance(value, str) else value) | 
					
						
							|  |  |  |             if len(encoded_value) > allocated_length: | 
					
						
							|  |  |  |                 raise ValueError("bytes/str item exceeds available storage") | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |             if current_format[-1] == "s": | 
					
						
							|  |  |  |                 new_format = current_format | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |                 new_format = self._types_mapping[str] % ( | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |                     allocated_length, | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self._set_packing_format_and_transform( | 
					
						
							|  |  |  |             position, | 
					
						
							|  |  |  |             new_format, | 
					
						
							|  |  |  |             value | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2020-04-20 20:54:55 +02:00
										 |  |  |         struct.pack_into(new_format, self.shm.buf, offset, encoded_value) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def __reduce__(self): | 
					
						
							|  |  |  |         return partial(self.__class__, name=self.shm.name), () | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |     def __len__(self): | 
					
						
							|  |  |  |         return struct.unpack_from("q", self.shm.buf, 0)[0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return f'{self.__class__.__name__}({list(self)}, name={self.shm.name!r})' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def format(self): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         "The struct packing format used by all currently stored items." | 
					
						
							| 
									
										
										
										
											2019-02-23 22:08:16 -06:00
										 |  |  |         return "".join( | 
					
						
							|  |  |  |             self._get_packing_format(i) for i in range(self._list_len) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def _format_size_metainfo(self): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         "The struct packing format used for the items' storage offsets." | 
					
						
							|  |  |  |         return "q" * (self._list_len + 1) | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def _format_packing_metainfo(self): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         "The struct packing format used for the items' packing formats." | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         return "8s" * self._list_len | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def _format_back_transform_codes(self): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         "The struct packing format used for the items' back transforms." | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  |         return "b" * self._list_len | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def _offset_data_start(self): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         # - 8 bytes for the list length | 
					
						
							|  |  |  |         # - (N + 1) * 8 bytes for the element offsets | 
					
						
							|  |  |  |         return (self._list_len + 2) * 8 | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def _offset_packing_formats(self): | 
					
						
							| 
									
										
										
										
											2020-04-19 11:19:24 -04:00
										 |  |  |         return self._offset_data_start + self._allocated_offsets[-1] | 
					
						
							| 
									
										
										
										
											2019-02-01 22:52:23 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def _offset_back_transform_codes(self): | 
					
						
							|  |  |  |         return self._offset_packing_formats + self._list_len * 8 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def count(self, value): | 
					
						
							|  |  |  |         "L.count(value) -> integer -- return number of occurrences of value." | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return sum(value == entry for entry in self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def index(self, value): | 
					
						
							|  |  |  |         """L.index(value) -> integer -- return first index of value.
 | 
					
						
							|  |  |  |         Raises ValueError if the value is not present."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for position, entry in enumerate(self): | 
					
						
							|  |  |  |             if value == entry: | 
					
						
							|  |  |  |                 return position | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-07-05 20:50:45 +03:00
										 |  |  |             raise ValueError("ShareableList.index(x): x not in list") | 
					
						
							| 
									
										
										
										
											2020-04-10 17:46:36 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     __class_getitem__ = classmethod(types.GenericAlias) |