Skip to content

Commit 9fe8f93

Browse files
committed
Implemented first basic functionality of cursor, which is only complete once some more of the memory manager is implemented. Next up is its major use_window method
1 parent cab0e3d commit 9fe8f93

4 files changed

Lines changed: 120 additions & 18 deletions

File tree

smmap/mman.py

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
11
"""Module containnig a memory memory manager which provides a sliding window on a number of memory mapped files"""
2-
3-
__all__ = ["MappedMemoryManager", "MemoryCursor"]
4-
52
from util import (
63
MemoryWindow,
74
MappedRegion,
85
MappedRegionList,
96
)
107

8+
from weakref import proxy
9+
10+
__all__ = ["MappedMemoryManager"]
11+
#{ Utilities
12+
13+
#}END utilities
1114

1215
class MemoryCursor(object):
1316
"""Pointer into the mapped region of the memory manager, keeping the current window
14-
alive until it is destroyed"""
17+
alive until it is destroyed.
18+
19+
Cursors should not be created manually, but are instead returned by the MappedMemoryManager"""
1520
__slots__ = (
1621
'_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
22+
'_rlist', # a regions list with regions for our file
23+
'_region', # our current region or None
1924
'_ofs', # relative offset from the actually mapped area to our start area
2025
'_size' # maximum size we should provide
2126
)
2227

2328
def __init__(self, manager = None, regions = None):
2429
self._manager = manager
25-
self._regions = regions
26-
self._region = region
30+
self._rlist = regions
31+
self._region = None
2732
self._ofs = 0
2833
self._size = 0
2934

@@ -32,12 +37,97 @@ def __del__(self):
3237

3338
def _destroy(self):
3439
"""Destruction code to decrement counters"""
40+
self.unuse_region()
41+
42+
if self._rlist is not None:
43+
# Actual client count, which doesn't include the reference kept by the manager, nor ours
44+
# as we are about to be deleted
45+
num_clients = self._rlist.client_count() - 2
46+
if num_clients == 0 and len(self._rlist) == 0:
47+
# Free all resources associated with the mapped file
48+
self._manager._files.pop(self._rlist.path())
49+
#END remove regions list from manager
50+
#END handle regions
3551

3652
def _copy_from(self, rhs):
3753
"""Copy all data from rhs into this instance, handles usage count"""
54+
self._manager = rhs._manager
55+
self._rlist = rhs._rlist
56+
self._region = rhs._region
57+
self._ofs = rhs._ofs
58+
self._size = rhs._size
59+
60+
if self._region is not None:
61+
self._region.increment_usage_count(1)
62+
# END handle regions
63+
64+
def __copy__(self):
65+
"""copy module interface"""
66+
cpy = type(self)()
67+
cpy._copy_from(self)
68+
return cpy
3869

3970
#{ Interface
71+
def assign(self, rhs):
72+
"""Assign rhs to this instance. This is required in order to get a real copy.
73+
Alternativly, you can copy an existing instance using the copy module"""
74+
self._destroy()
75+
self._copy_from(rhs)
76+
77+
def use_region(self, offset, size):
78+
"""Assure we point to a window which allows access to the given offset into the file
79+
:param offset: absolute offset in bytes into the file
80+
:param size: amount of bytes to map
81+
:return: this instance - it should be queried for whether it points to a valid memory region.
82+
This is not the case if the mapping failed becaues we reached the end of the file
83+
:note: The size actually mapped may be smaller than the given size. If that is the case,
84+
either the file has reached its end, or the map was created between two existing regions"""
85+
86+
def unuse_region(self):
87+
"""Unuse the ucrrent region. Does nothing if we have no current region
88+
:note: the cursor unuses the region automatically upon destruction. It is recommended
89+
to unuse the region once you are done reading from it in persistent cursors as it
90+
helps to free up resource more quickly"""
91+
self._region = None
4092

93+
def is_valid(self):
94+
""":return: True if we have a valid and usable region"""
95+
return self._region is not None
96+
97+
def is_associated(self):
98+
""":return: True if we are associated with a specific file already"""
99+
return self._rlist is not None
100+
101+
def ofs_begin(self):
102+
""":return: offset to the first byte pointed to by our cursor"""
103+
return self._region.ofs_begin() + self._ofs
104+
105+
def size(self):
106+
""":return: amount of bytes we point to"""
107+
return self._size
108+
109+
def region_ref(self):
110+
""":return: weak proxy to our mapped region.
111+
:raise AssertionError: if we have no current region. This is only useful for debugging"""
112+
if self._region is None:
113+
raise AssertionError("region not set")
114+
return proxy(self._region)
115+
116+
def includes_ofs(self, ofs):
117+
""":return: True if the given absolute offset is contained in the cursors
118+
current region
119+
:note: always False if the cursor does not point to a valid region"""
120+
if self._region is None:
121+
return False
122+
return (self.ofs_begin() <= ofs) and (ofs < self.ofs_end())
123+
124+
def file_size(self):
125+
""":return: size of the underlying file"""
126+
return self._rlist.file_size()
127+
128+
def path(self):
129+
""":return: path of the underlying mapped file"""
130+
return self._rlist.path()
41131

42132
#} END interface
43133

smmap/test/test_mman.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
from lib import TestBase, FileCreator
22

3+
from copy import copy
34
from smmap.mman import *
5+
from smmap.mman import MemoryCursor
46

57
class TestMMan(TestBase):
68

79
def test_cursor(self):
8-
man = MappedMemoryManager()
10+
man = MappedMemoryManager()
11+
c = MemoryCursor(man)
12+
assert not c.is_valid()
13+
assert not c.is_associated()
914

10-
def test_basics(self):
15+
# copy module
16+
17+
# assign method
18+
19+
20+
21+
def test_memory_manager(self):
1122
pass

smmap/test/test_util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ def test_region(self):
7575
rfull2 = rfull
7676
assert rfull.client_count() == 2
7777

78+
# usage
79+
assert rfull.usage_count() == 0
80+
rfull.increment_usage_count()
81+
assert rfull.usage_count() == 1
82+
7883
# window constructor
7984
w = MemoryWindow.from_region(rfull)
8085
assert w.ofs == rfull.ofs_begin() and w.ofs_end() == rfull.ofs_end()

smmap/util.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ class MappedRegion(object):
7575
'_b' , # beginning of mapping
7676
'_mf', # mapped memory chunk (as returned by mmap)
7777
'_uc', # total amount of usages
78-
'_ms', # actual size of the mapping
79-
'__weakref__' # allow weak references to a region
78+
'_ms' # actual size of the mapping
79+
'__weakref__'
8080
]
8181
_need_compat_layer = sys.version_info[1] < 6
8282

@@ -139,17 +139,13 @@ def client_count(self):
139139
# -1: self on stack, -1 self in this method, -1 self in getrefcount
140140
return getrefcount(self)-3
141141

142-
def adjust_client_count(self, ofs):
143-
"""Adjust the client count by the given positive or negative offset"""
144-
self._nc += ofs
145-
146142
def usage_count(self):
147143
""":return: amount of usages so far"""
148144
return self._uc
149145

150-
def adjust_usage_count(self, ofs):
146+
def increment_usage_count(self):
151147
"""Adjust the usage count by the given positive or negative offset"""
152-
self._uc += ofs
148+
self._uc += 1
153149

154150
# re-define all methods which need offset adjustments in compatibility mode
155151
if _need_compat_layer:

0 commit comments

Comments
 (0)