Skip to content

Commit 631b9ea

Browse files
committed
Implemented static memory manager, for now without test
1 parent 03dd5ae commit 631b9ea

3 files changed

Lines changed: 99 additions & 15 deletions

File tree

smmap/buf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Module with a simple buffer implementation using the memory manager"""
2-
from mman import MemoryCursor
2+
from mman import WindowCursor
33

44
import sys
55

smmap/mman.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@
99
from exc import RegionCollectionError
1010
from weakref import ref
1111
import sys
12+
from sys import getrefcount
1213

1314
__all__ = ["StaticWindowMapManager", "SlidingWindowMapManager"]
1415
#{ Utilities
1516

1617
#}END utilities
1718

18-
class MemoryCursor(object):
19-
"""Pointer into the mapped region of the memory manager, keeping the current window
20-
alive until it is destroyed.
19+
20+
21+
class WindowCursor(object):
22+
"""Pointer into the mapped region of the memory manager, keeping the map
23+
alive until it is destroyed and no other client uses it.
2124
22-
Cursors should not be created manually, but are instead returned by the SlidingWindowMapManager"""
25+
Cursors should not be created manually, but are instead returned by the SlidingWindowMapManager
26+
:note: The current implementation is suited for static and sliding window managers, but it also means
27+
that it must be suited for the somewhat quite different sliding manager. It could be improved, but
28+
I see no real need to do so."""
2329
__slots__ = (
2430
'_manager', # the manger keeping all file regions
2531
'_rlist', # a regions list with regions for our file
@@ -130,7 +136,14 @@ def buffer(self):
130136
:note: buffers should not be cached passed the duration of your access as it will
131137
prevent resources from being freed even though they might not be accounted for anymore !"""
132138
return buffer(self._region.map(), self._ofs, self._size)
133-
139+
140+
def map(self):
141+
"""
142+
:return: the underlying raw memory map. Please not that the offset and size is likely to be different
143+
to what you set as offset and size. Use it only if you are sure about the region it maps, which is the whole
144+
file in case of StaticWindowMapManager"""
145+
return self._region.map()
146+
134147
def is_valid(self):
135148
""":return: True if we have a valid and usable region"""
136149
return self._region is not None
@@ -188,7 +201,7 @@ def fd(self):
188201
:note: it is not required to be valid anymore
189202
:raise ValueError: if the mapping was not created by a file descriptor"""
190203
if isinstance(self._rlist.path_or_fd(), basestring):
191-
return ValueError("File descriptor queried although mapping was generated from path")
204+
raise ValueError("File descriptor queried although mapping was generated from path")
192205
#END handle type
193206
return self._rlist.path_or_fd()
194207

@@ -208,7 +221,7 @@ class StaticWindowMapManager(object):
208221
acomodate this fact"""
209222

210223
__slots__ = [
211-
'_fdict', # mapping of path -> MapRegionList
224+
'_fdict', # mapping of path -> StorageHelper (of some kind
212225
'_window_size', # maximum size of a window
213226
'_max_memory_size', # maximum amount ofmemory we may allocate
214227
'_max_handle_count', # maximum amount of handles to keep open
@@ -220,7 +233,7 @@ class StaticWindowMapManager(object):
220233
MapRegionListCls = MapRegionList
221234
MapWindowCls = MapWindow
222235
MapRegionCls = MapRegion
223-
MemoryCursorCls = MemoryCursor
236+
WindowCursorCls = WindowCursor
224237
#} END configuration
225238

226239
_MB_in_bytes = 1024 * 1024
@@ -266,14 +279,77 @@ def _collect_lru_region(self, size):
266279
:raise RegionCollectionError:
267280
:return: Amount of freed regions
268281
:todo: implement a case where all unusued regions are discarded efficiently. Currently its only brute force"""
269-
raise NotImplementedError()
282+
num_found = 0
283+
while (size == 0) or (self._memory_size + size > self._max_memory_size):
284+
for k, regions in self._fdict.iteritems():
285+
found_lonely_region = False
286+
for region in regions:
287+
# check client count - consider that we keep one reference ourselves !
288+
if (region.client_count()-2 == 0 and
289+
(lru_region is None or region._uc < lru_region._uc)):
290+
# remove whole list
291+
found_lonely_region = True
292+
num_found += 1
293+
self._memory_size -= region.size()
294+
self._handle_count -= 1
295+
self._fdict.pop(k)
296+
297+
break
298+
# END update lru_region
299+
#END for each region
300+
if found_lonely_region:
301+
continue
302+
# END skip iteration and restart
303+
#END for each regions list
304+
305+
# still here ?
306+
if num_found == 0 and size != 0:
307+
raise RegionCollectionError("Didn't find any region to free")
308+
#END raise if necessary
309+
#END while there is more memory to free
310+
311+
return num_found
312+
270313

271314
def _obtain_region(self, a, offset, size, flags, is_recursive):
272315
"""Utilty to create a new region - for more information on the parameters,
273316
see MapCursor.use_region.
274317
:param a: A regions (a)rray
275318
:return: The newly created region"""
276-
raise NotImplementedError()
319+
if self._memory_size + window_size > self._max_memory_size:
320+
self._collect_lru_region(window_size)
321+
#END handle collection
322+
323+
r = None
324+
if a:
325+
assert len(a) == 1
326+
r = a[0]
327+
else:
328+
try:
329+
r = self.MapRegionCls(a.path_or_fd(), 0, sys.maxint, flags)
330+
except Exception:
331+
# apparently we are out of system resources or hit a limit
332+
# As many more operations are likely to fail in that condition (
333+
# like reading a file from disk, etc) we free up as much as possible
334+
# As this invalidates our insert position, we have to recurse here
335+
# NOTE: The c++ version uses a linked list to curcumvent this, but
336+
# using that in python is probably too slow anyway
337+
if is_recursive:
338+
# we already tried this, and still have no success in obtaining
339+
# a mapping. This is an exception, so we propagate it
340+
raise
341+
#END handle existing recursion
342+
self._collect_lru_region(0)
343+
return self._obtain_region(a, offset, size, flags, True)
344+
#END handle exceptions
345+
346+
self._handle_count += 1
347+
self._memory_size += r.size()
348+
# END handle array
349+
350+
assert a.includes_ofs(offset)
351+
assert a.includes_ofs(offset + size-1)
352+
return r
277353

278354
#}END internal methods
279355

@@ -293,7 +369,7 @@ def make_cursor(self, path_or_fd):
293369
regions = self.MapRegionListCls(path_or_fd)
294370
self._fdict[path_or_fd] = regions
295371
# END obtain region for path
296-
return self.MemoryCursorCls(self, regions)
372+
return self.WindowCursorCls(self, regions)
297373

298374
def collect(self):
299375
"""Collect all available free-to-collect mapped regions

smmap/test/test_mman.py

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

33
from smmap.mman import *
4-
from smmap.mman import MemoryCursor
4+
from smmap.mman import WindowCursor
55
from smmap.util import align_to_mmap
66
from smmap.exc import RegionCollectionError
77

@@ -17,7 +17,7 @@ def test_cursor(self):
1717
fc = FileCreator(self.k_window_test_size, "cursor_test")
1818

1919
man = SlidingWindowMapManager()
20-
ci = MemoryCursor(man) # invalid cursor
20+
ci = WindowCursor(man) # invalid cursor
2121
assert not ci.is_valid()
2222
assert not ci.is_associated()
2323
assert ci.size() == 0 # this is cached, so we can query it in invalid state
@@ -43,7 +43,7 @@ def test_cursor(self):
4343

4444
# destruction is fine (even multiple times)
4545
cv._destroy()
46-
MemoryCursor(man)._destroy()
46+
WindowCursor(man)._destroy()
4747

4848
def test_memory_manager(self):
4949
man = SlidingWindowMapManager()
@@ -65,11 +65,19 @@ def test_memory_manager(self):
6565
fd = os.open(fc.path, os.O_RDONLY)
6666
for item in (fc.path, fd):
6767
c = man.make_cursor(item)
68+
assert c.path_or_fd() is item
6869
assert c.use_region(10, 10).is_valid()
6970
assert c.ofs_begin() == 10
7071
assert c.size() == 10
7172
assert c.buffer()[:] == open(fc.path, 'rb').read(20)[10:]
73+
74+
if isinstance(item, int):
75+
self.failUnlessRaises(ValueError, c.path)
76+
else:
77+
self.failUnlessRaises(ValueError, c.fd)
78+
#END handle value error
7279
#END for each input
80+
7381
os.close(fd)
7482

7583
def test_memman_operation(self):

0 commit comments

Comments
 (0)