Skip to content

Commit cab0e3d

Browse files
committed
Moved all utility types into their own module
1 parent 25e5035 commit cab0e3d

5 files changed

Lines changed: 309 additions & 268 deletions

File tree

smmap/mman.py

Lines changed: 29 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,192 +1,46 @@
11
"""Module containnig a memory memory manager which provides a sliding window on a number of memory mapped files"""
22

3-
__all__ = ["MappedMemoryManager"]
3+
__all__ = ["MappedMemoryManager", "MemoryCursor"]
44

5-
import os
6-
import sys
7-
import mmap
8-
9-
from mmap import PAGESIZE
10-
from sys import getrefcount
11-
12-
#{ Utilities
13-
14-
def align_to_page(num, round_up):
15-
"""Align the given integer number to the closest page offset, which usually is 4096 bytes.
16-
:param round_up: if True, the next higher multiple of page size is used, otherwise
17-
the lower page_size will be used (i.e. if True, 1 becomes 4096, otherwise it becomes 0)
18-
:return: num rounded to closest page"""
19-
res = (num / PAGESIZE) * PAGESIZE;
20-
if round_up and (res != num):
21-
res += PAGESIZE;
22-
#END handle size
23-
return res;
24-
25-
#}END utilities
26-
27-
class Window(object):
28-
"""Utility type which is used to snap windows towards each other, and to adjust their size"""
29-
__slots__ = (
30-
'ofs', # offset into the file in bytes
31-
'size' # size of the window in bytes
5+
from util import (
6+
MemoryWindow,
7+
MappedRegion,
8+
MappedRegionList,
329
)
3310

34-
def __init__(self, offset, size):
35-
self.ofs = offset
36-
self.size = size
37-
38-
def __repr__(self):
39-
return "Window(%i, %i)" % (self.ofs, self.size)
40-
41-
@classmethod
42-
def from_region(cls, region):
43-
""":return: new window from a region"""
44-
return cls(region.ofs_begin(), region.size())
45-
46-
def ofs_end(self):
47-
return self.ofs + self.size
4811

49-
def align(self):
50-
self.ofs = align_to_page(self.ofs, 0)
51-
self.size = align_to_page(self.size, 1)
52-
53-
def extend_left_to(self, window, max_size):
54-
"""Adjust the offset to start where the given window on our left ends if possible,
55-
but don't make yourself larger than max_size.
56-
The resize will assure that the new window still contains the old window area"""
57-
rofs = self.ofs - window.ofs_end()
58-
nsize = rofs + self.size
59-
rofs -= nsize - min(nsize, max_size)
60-
self.ofs = self.ofs - rofs
61-
self.size += rofs
62-
63-
def extend_right_to(self, window, max_size):
64-
"""Adjust the size to make our window end where the right window begins, but don't
65-
get larger than max_size"""
66-
self.size = min(self.size + (window.ofs - self.ofs_end()), max_size)
67-
68-
69-
class MappedRegion(object):
70-
"""Defines a mapped region of memory, aligned to pagesizes
71-
:note: deallocates used region automatically on destruction"""
72-
__slots__ = [
73-
'_b' , # beginning of mapping
74-
'_mf', # mapped memory chunk (as returned by mmap)
75-
'_uc', # total amount of usages
76-
'_ms' # actual size of the mapping
77-
]
78-
_need_compat_layer = sys.version_info[1] < 6
79-
80-
if _need_compat_layer:
81-
__slots__.append('_mfb') # mapped memory buffer to provide offset
82-
#END handle additional slot
83-
12+
class MemoryCursor(object):
13+
"""Pointer into the mapped region of the memory manager, keeping the current window
14+
alive until it is destroyed"""
15+
__slots__ = (
16+
'_manager', # the manger keeping all file regions
17+
'_regions', # a regions list with regions for our file
18+
'_region', # WEAK REF to our current region
19+
'_ofs', # relative offset from the actually mapped area to our start area
20+
'_size' # maximum size we should provide
21+
)
8422

85-
def __init__(self, path, ofs, size):
86-
"""Initialize a region, allocate the memory map
87-
:param path: path to the file to map
88-
:param ofs: **aligned** offset into the file to be mapped
89-
:param size: if size is larger then the file on disk, the whole file will be
90-
allocated the the size automatically adjusted
91-
:raise Exception: if no memory can be allocated"""
92-
self._b = ofs
93-
self._uc = 0
94-
95-
fd = os.open(path, os.O_RDONLY|getattr(os, 'O_BINARY', 0))
96-
try:
97-
kwargs = dict(access=mmap.ACCESS_READ, offset=ofs)
98-
corrected_size = size
99-
sizeofs = ofs
100-
if self._need_compat_layer:
101-
del(kwargs['offset'])
102-
corrected_size += ofs
103-
sizeofs = 0
104-
# END handle python not supporting offset ! Arg
105-
106-
# have to correct size, otherwise (instead of the c version) it will
107-
# bark that the size is too large ... many extra file accesses because
108-
# if this ... argh !
109-
self._mf = mmap.mmap(fd, min(os.fstat(fd).st_size - sizeofs, corrected_size - sizeofs), **kwargs)
110-
111-
if self._need_compat_layer:
112-
self._mfb = buffer(self._mf, ofs, size)
113-
#END handle buffer wrapping
114-
finally:
115-
os.close(fd)
116-
#END close file handle
117-
118-
def ofs_begin(self):
119-
""":return: absolute byte offset to the first byte of the mapping"""
120-
return self._b
121-
122-
def size(self):
123-
""":return: total size of the mapped region in bytes"""
124-
return len(self._mf)
23+
def __init__(self, manager = None, regions = None):
24+
self._manager = manager
25+
self._regions = regions
26+
self._region = region
27+
self._ofs = 0
28+
self._size = 0
12529

126-
def ofs_end(self):
127-
""":return: Absolute offset to one byte beyond the mapping into the file"""
128-
return self._b + self.size()
30+
def __del__(self):
31+
self._destroy()
12932

130-
def includes_ofs(self, ofs):
131-
""":return: True if the given offset can be read in our mapped region"""
132-
return (ofs >= self.ofs_begin()) and (ofs <= self.ofs_end())
33+
def _destroy(self):
34+
"""Destruction code to decrement counters"""
13335

134-
def client_count(self):
135-
""":return: number of clients currently using this region"""
136-
# -1: self on stack, -1 self in this method, -1 self in getrefcount
137-
return getrefcount(self)-3
36+
def _copy_from(self, rhs):
37+
"""Copy all data from rhs into this instance, handles usage count"""
13838

139-
def adjust_client_count(self, ofs):
140-
"""Adjust the client count by the given positive or negative offset"""
141-
self._nc += ofs
142-
143-
def usage_count(self):
144-
""":return: amount of usages so far"""
145-
return self._uc
146-
147-
def adjust_usage_count(self, ofs):
148-
"""Adjust the usage count by the given positive or negative offset"""
149-
self._uc += ofs
150-
151-
# re-define all methods which need offset adjustments in compatibility mode
152-
if _need_compat_layer:
153-
def size(self):
154-
return len(self._mf) - self._b
155-
156-
def ofs_end(self):
157-
return len(self._mf)
158-
#END handle compat layer
39+
#{ Interface
15940

160-
161-
class MappedRegionList(list):
162-
"""List of MappedRegion instances associating a path with a list of regions."""
163-
__slots__ = (
164-
'_path', # path which is mapped by all our regions
165-
'_file_size' # total size of the file we map
166-
)
167-
168-
def __new__(cls, path):
169-
return super(MappedRegionList, cls).__new__(cls)
17041

171-
def __init__(self, path):
172-
self._path = path
173-
self._file_size = None
174-
175-
def path(self):
176-
""":return: path to file whose regions we manage"""
177-
return self._path
178-
179-
def file_size(self):
180-
""":return: size of file we manager"""
181-
if self._file_size is None:
182-
self._file_size = os.stat(self._path).st_size
183-
#END update file size
184-
return self._file_size
42+
#} END interface
18543

186-
187-
class Cursor(object):
188-
"""Pointer into the mapped region of the memory manager, keeping the current window
189-
alive until it is destroyed"""
19044

19145

19246
class MappedMemoryManager(object):

smmap/test/lib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class TestBase(TestCase):
4949
"""Foundation used by all tests"""
5050

5151
#{ Configuration
52-
52+
k_window_test_size = 1000 * 1000 * 8 + 5195
5353
#} END configuration
5454

5555
#{ Overrides

smmap/test/test_mman.py

Lines changed: 1 addition & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,11 @@
11
from lib import TestBase, FileCreator
22

33
from smmap.mman import *
4-
from smmap.mman import align_to_page
5-
from smmap.mman import Window
6-
from smmap.mman import MappedRegion
7-
from smmap.mman import MappedRegionList
8-
from smmap.mman import Cursor
9-
10-
import sys
11-
import mmap
124

135
class TestMMan(TestBase):
146

15-
_window_test_size = 1000 * 1000 * 8 + 5195
16-
17-
def test_window(self):
18-
wl = Window(0, 1) # left
19-
wc = Window(1, 1) # center
20-
wc2 = Window(10, 5) # another center
21-
wr = Window(8000, 50) # right
22-
23-
assert wl.ofs_end() == 1
24-
assert wc.ofs_end() == 2
25-
assert wr.ofs_end() == 8050
26-
27-
# extension does nothing if already in place
28-
maxsize = 100
29-
wc.extend_left_to(wl, maxsize)
30-
assert wc.ofs == 1 and wc.size == 1
31-
wl.extend_right_to(wc, maxsize)
32-
wl.extend_right_to(wc, maxsize)
33-
assert wl.ofs == 0 and wl.size == 1
34-
35-
# an actual left extension
36-
pofs_end = wc2.ofs_end()
37-
wc2.extend_left_to(wc, maxsize)
38-
assert wc2.ofs == wc.ofs_end() and pofs_end == wc2.ofs_end()
39-
40-
41-
# respects maxsize
42-
wc.extend_right_to(wr, maxsize)
43-
assert wc.ofs == 1 and wc.size == maxsize
44-
wc.extend_right_to(wr, maxsize)
45-
assert wc.ofs == 1 and wc.size == maxsize
46-
47-
# without maxsize
48-
wc.extend_right_to(wr, sys.maxint)
49-
assert wc.ofs_end() == wr.ofs and wc.ofs == 1
50-
51-
# extend left
52-
wr.extend_left_to(wc2, maxsize)
53-
wr.extend_left_to(wc2, maxsize)
54-
assert wr.size == maxsize
55-
56-
wr.extend_left_to(wc2, sys.maxint)
57-
assert wr.ofs == wc2.ofs_end()
58-
59-
wc.align()
60-
assert wc.ofs == 0 and wc.size == mmap.PAGESIZE*2
61-
62-
def test_region(self):
63-
fc = FileCreator(self._window_test_size, "window_test")
64-
half_size = fc.size / 2
65-
rofs = align_to_page(4200, False)
66-
rfull = MappedRegion(fc.path, 0, fc.size)
67-
rhalfofs = MappedRegion(fc.path, rofs, fc.size)
68-
rhalfsize = MappedRegion(fc.path, 0, half_size)
69-
70-
# offsets
71-
assert rfull.ofs_begin() == 0 and rfull.size() == fc.size
72-
assert rfull.ofs_end() == fc.size # if this method works, it works always
73-
74-
assert rhalfofs.ofs_begin() == rofs and rhalfofs.size() == fc.size - rofs
75-
assert rhalfsize.ofs_begin() == 0 and rhalfsize.size() == half_size
76-
77-
assert rfull.includes_ofs(0) and rfull.includes_ofs(fc.size-1) and rfull.includes_ofs(half_size)
78-
assert not rfull.includes_ofs(-1) and not rfull.includes_ofs(sys.maxint)
79-
assert rhalfofs.includes_ofs(rofs) and not rhalfofs.includes_ofs(0)
80-
81-
# auto-refcount
82-
assert rfull.client_count() == 1
83-
rfull2 = rfull
84-
assert rfull.client_count() == 2
85-
86-
# window constructor
87-
w = Window.from_region(rfull)
88-
assert w.ofs == rfull.ofs_begin() and w.ofs_end() == rfull.ofs_end()
89-
90-
def test_region_list(self):
91-
fc = FileCreator(100, "sample_file")
92-
ml = MappedRegionList(fc.path)
93-
94-
assert len(ml) == 0
95-
assert ml.path() == fc.path
96-
assert ml.file_size() == fc.size
97-
987
def test_cursor(self):
99-
pass
8+
man = MappedMemoryManager()
1009

10110
def test_basics(self):
10211
pass

0 commit comments

Comments
 (0)