Skip to content

Commit c30e930

Browse files
committed
Changed buffer implementation to use a cursor right away instead of taking a path and a manager. This makes it much more flexible, as it doesn't have to care about the manager anymore, making it easier to use and making clear that it is meant for use with a mapped memory manager implementation.
1 parent 066ee2f commit c30e930

2 files changed

Lines changed: 35 additions & 45 deletions

File tree

smmap/buf.py

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,23 @@
77

88
class MappedMemoryBuffer(object):
99
"""A buffer like object which allows direct byte-wise object and slicing into
10-
memory of a mapped file. The mapping is controlled by an underlying memory manager.
11-
12-
A buffer, once initialized, stays put on providing access to eactly one path.
13-
A custom interface allows you to change paths mid way, and to optimize
14-
the resource usage.
15-
16-
Please note that this type is only fully usable if you configure it with the
17-
MappedMemoryManager to use.
10+
memory of a mapped file. The mapping is controlled by the provided cursor.
1811
1912
The buffer is relative, that is if you map an offset, index 0 will map to the
20-
first byte at your given offset."""
13+
first byte at the offset you used during initialization or begin_access"""
2114
__slots__ = '_c' # our cursor
2215

23-
#{ Configuration
24-
# A subclass must provide an instance of a (usually global) MappedMemoryManager
25-
manager = None
26-
#}END configuration
2716

28-
def __init__(self, path = None, offset = 0, size = sys.maxint, flags = 0):
29-
"""Initalize the instance to operate on the given path if given.
30-
:param path: if not None, the path to the file you want to access
31-
If None, you have call begin_access before using the buffer
17+
def __init__(self, cursor = None, offset = 0, size = sys.maxint, flags = 0):
18+
"""Initalize the instance to operate on the given cursor.
19+
:param cursor: if not None, the associated cursor to the file you want to access
20+
If None, you have call begin_access before using the buffer and provide a cursor
3221
:param offset: absolute offset in bytes
3322
:param size: the total size of the mapping. Defaults to the maximum possible size
3423
:param flags: Additional flags to be passed to os.open
3524
:raise ValueError: if the buffer could not achieve a valid state"""
36-
self._c = MemoryCursor(self.manager)
37-
assert self.manager is not None, "Require the cls.manager variable to be set in subclass"
38-
if path and not self.begin_access(path, offset, size, flags):
25+
self._c = cursor
26+
if cursor and not self.begin_access(cursor, offset, size, flags):
3927
raise ValueError("Failed to allocate the buffer - probably the given offset is out of bounds")
4028
# END handle offset
4129

@@ -75,19 +63,19 @@ def __getslice__(self, i, j):
7563
# END fast or slow path
7664
#{ Interface
7765

78-
def begin_access(self, path = None, offset = 0, size = sys.maxint, flags = 0):
66+
def begin_access(self, cursor = None, offset = 0, size = sys.maxint, flags = 0):
7967
"""Call this before the first use of this instance. The method was already
8068
called by the constructor in case sufficient information was provided.
8169
8270
For more information no the parameters, see the __init__ method
83-
:param path: if path is empty or None the existing path will be used if possible.
71+
:param path: if cursor is None the existing one will be used.
8472
:return: True if the buffer can be used"""
85-
if path and (not self._c.is_associated() or self._c.path() != path):
86-
self._c = self.manager.make_cursor(path)
87-
#END get associated cursor
73+
if cursor:
74+
self._c = cursor
75+
#END update our cursor
8876

8977
# reuse existing cursors if possible
90-
if self._c.is_associated():
78+
if self._c is not None and self._c.is_associated():
9179
return self._c.use_region(offset, size, flags).is_valid()
9280
return False
9381

@@ -97,7 +85,9 @@ def end_access(self):
9785
resources to be freed.
9886
9987
Once you called end_access, you must call begin access before reusing this instance!"""
100-
self._c.unuse_region()
88+
if self._c is not None:
89+
self._c.unuse_region()
90+
#END unuse region
10191

10292
def cursor(self):
10393
""":return: the currently set cursor which provides access to the data"""

smmap/test/test_buf.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,34 @@
77
from time import time
88
import sys
99

10-
class TestBuffer(MappedMemoryBuffer):
11-
#{ Configuration
12-
manager = MappedMemoryManager()
13-
#} END configuration
14-
10+
11+
man_optimal = MappedMemoryManager()
12+
man_worst_case = MappedMemoryManager( window_size=TestBase.k_window_test_size/100,
13+
max_memory_size=TestBase.k_window_test_size/3,
14+
max_open_handles=15)
1515

1616
class TestBuf(TestBase):
1717

1818
def test_basics(self):
19-
self.failUnlessRaises(AssertionError, MappedMemoryBuffer) # needs subclass
2019
fc = FileCreator(self.k_window_test_size, "buffer_test")
2120

2221
# invalid paths fail upon construction
23-
self.failUnlessRaises(OSError, TestBuffer, "somefile") # invalid file
24-
self.failUnlessRaises(ValueError, TestBuffer, fc.path, fc.size) # offset too large
22+
c = man_optimal.make_cursor(fc.path)
23+
self.failUnlessRaises(ValueError, MappedMemoryBuffer, type(c)()) # invalid cursor
24+
self.failUnlessRaises(ValueError, MappedMemoryBuffer, c, fc.size) # offset too large
2525

26-
buf = TestBuffer() # can create uninitailized buffers
27-
assert not buf.cursor().is_valid() and not buf.cursor().is_associated()
26+
buf = MappedMemoryBuffer() # can create uninitailized buffers
27+
assert buf.cursor() is None
2828

2929
# can call end access any time
3030
buf.end_access()
3131
buf.end_access()
3232

3333
# begin access can revive it, if the offset is suitable
3434
offset = 100
35-
assert buf.begin_access(fc.path, fc.size) == False
36-
assert buf.begin_access(fc.path, offset) == True
35+
assert buf.begin_access(c, fc.size) == False
36+
assert buf.begin_access(c, offset) == True
37+
assert buf.cursor().is_valid()
3738

3839
# empty begin access keeps it valid on the same path, but alters the offset
3940
assert buf.begin_access() == True
@@ -52,20 +53,19 @@ def test_basics(self):
5253
# an empty begin access fixes it up again
5354
assert buf.begin_access() == True and buf.cursor().is_valid()
5455
del(buf) # ends access automatically
56+
del(c)
5557

56-
man = TestBuffer.manager
57-
assert man.num_file_handles() == 1
58+
assert man_optimal.num_file_handles() == 1
5859

5960
# PERFORMANCE
6061
# blast away with rnadom access and a full mapping - we don't want to
6162
# exagerate the manager's overhead, but measure the buffer overhead
6263
# We do it once with an optimal setting, and with a worse manager which
6364
# will produce small mappings only !
6465
max_num_accesses = 1000
65-
for manager, man_id in ( (man, 'optimal'),
66-
(MappedMemoryManager(window_size=fc.size/100, max_memory_size=fc.size/3, max_open_handles=15), 'worst case')):
67-
TestBuffer.manager = manager
68-
buf = TestBuffer(fc.path)
66+
for manager, man_id in ( (man_optimal, 'optimal'),
67+
(man_worst_case, 'worst case')):
68+
buf = MappedMemoryBuffer(manager.make_cursor(fc.path))
6969
assert manager.num_file_handles() == 1
7070
for access_mode in range(2): # single, multi
7171
num_accesses_left = max_num_accesses

0 commit comments

Comments
 (0)