Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/smmap/util.py: 38%
96 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""Module containing a memory memory manager which provides a sliding window on a number of memory mapped files"""
2import os
3import sys
5from mmap import mmap, ACCESS_READ
6from mmap import ALLOCATIONGRANULARITY
8__all__ = ["align_to_mmap", "is_64_bit",
9 "MapWindow", "MapRegion", "MapRegionList", "ALLOCATIONGRANULARITY"]
11#{ Utilities
14def align_to_mmap(num, round_up):
15 """
16 Align the given integer number to the closest page offset, which usually is 4096 bytes.
18 :param round_up: if True, the next higher multiple of page size is used, otherwise
19 the lower page_size will be used (i.e. if True, 1 becomes 4096, otherwise it becomes 0)
20 :return: num rounded to closest page"""
21 res = (num // ALLOCATIONGRANULARITY) * ALLOCATIONGRANULARITY
22 if round_up and (res != num):
23 res += ALLOCATIONGRANULARITY
24 # END handle size
25 return res
28def is_64_bit():
29 """:return: True if the system is 64 bit. Otherwise it can be assumed to be 32 bit"""
30 return sys.maxsize > (1 << 32) - 1
32#}END utilities
35#{ Utility Classes
37class MapWindow:
39 """Utility type which is used to snap windows towards each other, and to adjust their size"""
40 __slots__ = (
41 'ofs', # offset into the file in bytes
42 'size' # size of the window in bytes
43 )
45 def __init__(self, offset, size):
46 self.ofs = offset
47 self.size = size
49 def __repr__(self):
50 return "MapWindow(%i, %i)" % (self.ofs, self.size)
52 @classmethod
53 def from_region(cls, region):
54 """:return: new window from a region"""
55 return cls(region._b, region.size())
57 def ofs_end(self):
58 return self.ofs + self.size
60 def align(self):
61 """Assures the previous window area is contained in the new one"""
62 nofs = align_to_mmap(self.ofs, 0)
63 self.size += self.ofs - nofs # keep size constant
64 self.ofs = nofs
65 self.size = align_to_mmap(self.size, 1)
67 def extend_left_to(self, window, max_size):
68 """Adjust the offset to start where the given window on our left ends if possible,
69 but don't make yourself larger than max_size.
70 The resize will assure that the new window still contains the old window area"""
71 rofs = self.ofs - window.ofs_end()
72 nsize = rofs + self.size
73 rofs -= nsize - min(nsize, max_size)
74 self.ofs = self.ofs - rofs
75 self.size += rofs
77 def extend_right_to(self, window, max_size):
78 """Adjust the size to make our window end where the right window begins, but don't
79 get larger than max_size"""
80 self.size = min(self.size + (window.ofs - self.ofs_end()), max_size)
83class MapRegion:
85 """Defines a mapped region of memory, aligned to pagesizes
87 **Note:** deallocates used region automatically on destruction"""
88 __slots__ = [
89 '_b', # beginning of mapping
90 '_mf', # mapped memory chunk (as returned by mmap)
91 '_uc', # total amount of usages
92 '_size', # cached size of our memory map
93 '__weakref__'
94 ]
96 #{ Configuration
97 #} END configuration
99 def __init__(self, path_or_fd, ofs, size, flags=0):
100 """Initialize a region, allocate the memory map
101 :param path_or_fd: path to the file to map, or the opened file descriptor
102 :param ofs: **aligned** offset into the file to be mapped
103 :param size: if size is larger then the file on disk, the whole file will be
104 allocated the the size automatically adjusted
105 :param flags: additional flags to be given when opening the file.
106 :raise Exception: if no memory can be allocated"""
107 self._b = ofs
108 self._size = 0
109 self._uc = 0
111 if isinstance(path_or_fd, int):
112 fd = path_or_fd
113 else:
114 fd = os.open(path_or_fd, os.O_RDONLY | getattr(os, 'O_BINARY', 0) | flags)
115 # END handle fd
117 try:
118 kwargs = dict(access=ACCESS_READ, offset=ofs)
119 corrected_size = size
120 sizeofs = ofs
122 # have to correct size, otherwise (instead of the c version) it will
123 # bark that the size is too large ... many extra file accesses because
124 # if this ... argh !
125 actual_size = min(os.fstat(fd).st_size - sizeofs, corrected_size)
126 self._mf = mmap(fd, actual_size, **kwargs)
127 # END handle memory mode
129 self._size = len(self._mf)
130 finally:
131 if isinstance(path_or_fd, str):
132 os.close(fd)
133 # END only close it if we opened it
134 # END close file handle
135 # We assume the first one to use us keeps us around
136 self.increment_client_count()
138 def __repr__(self):
139 return "MapRegion<%i, %i>" % (self._b, self.size())
141 #{ Interface
143 def buffer(self):
144 """:return: a buffer containing the memory"""
145 return self._mf
147 def map(self):
148 """:return: a memory map containing the memory"""
149 return self._mf
151 def ofs_begin(self):
152 """:return: absolute byte offset to the first byte of the mapping"""
153 return self._b
155 def size(self):
156 """:return: total size of the mapped region in bytes"""
157 return self._size
159 def ofs_end(self):
160 """:return: Absolute offset to one byte beyond the mapping into the file"""
161 return self._b + self._size
163 def includes_ofs(self, ofs):
164 """:return: True if the given offset can be read in our mapped region"""
165 return self._b <= ofs < self._b + self._size
167 def client_count(self):
168 """:return: number of clients currently using this region"""
169 return self._uc
171 def increment_client_count(self, ofs = 1):
172 """Adjust the usage count by the given positive or negative offset.
173 If usage count equals 0, we will auto-release our resources
174 :return: True if we released resources, False otherwise. In the latter case, we can still be used"""
175 self._uc += ofs
176 assert self._uc > -1, "Increments must match decrements, usage counter negative: %i" % self._uc
178 if self.client_count() == 0:
179 self.release()
180 return True
181 else:
182 return False
183 # end handle release
185 def release(self):
186 """Release all resources this instance might hold. Must only be called if there usage_count() is zero"""
187 self._mf.close()
189 #} END interface
192class MapRegionList(list):
194 """List of MapRegion instances associating a path with a list of regions."""
195 __slots__ = (
196 '_path_or_fd', # path or file descriptor which is mapped by all our regions
197 '_file_size' # total size of the file we map
198 )
200 def __new__(cls, path):
201 return super().__new__(cls)
203 def __init__(self, path_or_fd):
204 self._path_or_fd = path_or_fd
205 self._file_size = None
207 def path_or_fd(self):
208 """:return: path or file descriptor we are attached to"""
209 return self._path_or_fd
211 def file_size(self):
212 """:return: size of file we manager"""
213 if self._file_size is None:
214 if isinstance(self._path_or_fd, str):
215 self._file_size = os.stat(self._path_or_fd).st_size
216 else:
217 self._file_size = os.fstat(self._path_or_fd).st_size
218 # END handle path type
219 # END update file size
220 return self._file_size
222#} END utility classes