Merge lp:~verterok/ubuntuone-client/fix-987376 into lp:ubuntuone-client

Proposed by Guillermo Gonzalez
Status: Work in progress
Proposed branch: lp:~verterok/ubuntuone-client/fix-987376
Merge into: lp:ubuntuone-client
Diff against target: 250 lines (+76/-39)
2 files modified
tests/syncdaemon/test_tritcask.py (+40/-13)
ubuntuone/syncdaemon/tritcask.py (+36/-26)
To merge this branch: bzr merge lp:~verterok/ubuntuone-client/fix-987376
Reviewer Review Type Date Requested Status
Facundo Batista (community) Approve
Manuel de la Peña (community) Approve
Review via email: mp+103273@code.launchpad.net

Commit message

Only use mmap in files of size < 2**31, in order to avoid hitting address space limits, and fallback to standard IO.

Description of the change

Only use mmap in files of size < 2**31, in order to avoid hitting address space limits, and fallback to standard IO.
I'll be working on limiting the file growth in a following branch, to fix Bug #987382.

To post a comment you must log in.
Revision history for this message
Facundo Batista (facundo) wrote :

Thanks!

review: Approve
Revision history for this message
Manuel de la Peña (mandel) wrote :

The following happens on widows:

[ERROR]
Traceback (most recent call last):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\tests\syn
cdaemon\test_tritcask.py", line 214, in test_iter_entries_bad_crc
    for i, entry in enumerate(db.live_file.iter_entries()):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\ubuntuone
\syncdaemon\tritcask.py", line 257, in iter_entries
    for entry in self._iter_entries(fmmap):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\ubuntuone
\syncdaemon\tritcask.py", line 266, in _iter_entries
    entry, new_pos = self.read(fd)
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\ubuntuone
\syncdaemon\tritcask.py", line 323, in read
    data = fd.read(key_sz+value_sz)
exceptions.OverflowError: Python int too large to convert to C long

tests.syncdaemon.test_tritcask.DataFileTest.test_iter_entries_bad_crc
===============================================================================
[ERROR]
Traceback (most recent call last):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\tests\syn
cdaemon\test_tritcask.py", line 214, in test_iter_entries_bad_crc
    for i, entry in enumerate(db.live_file.iter_entries()):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\ubuntuone
\syncdaemon\tritcask.py", line 252, in iter_entries
    for entry in self._iter_entries(self.fd):
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\ubuntuone
\syncdaemon\tritcask.py", line 266, in _iter_entries
    entry, new_pos = self.read(fd)
  File "C:\Users\Mandel\Projects\Canonical\ubuntuone-client\fix-987376\ubuntuone
\syncdaemon\tritcask.py", line 323, in read
    data = fd.read(key_sz+value_sz)
exceptions.MemoryError:

tests.syncdaemon.test_tritcask.NoMmapDataFileTest.test_iter_entries_bad_crc

review: Needs Fixing
Revision history for this message
Guillermo Gonzalez (verterok) wrote :

Thanks for spotting that one.
It's fixed and pushed.

Revision history for this message
Manuel de la Peña (mandel) wrote :

Everything works in all currently supported platforms!

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (247.1 KiB)

The attempt to merge lp:~verterok/ubuntuone-client/fix-987376 into lp:ubuntuone-client failed. Below is the output from the failed tests.

/usr/bin/gnome-autogen.sh
checking for autoconf >= 2.53...
  testing autoconf2.50... not found.
  testing autoconf... found 2.68
checking for automake >= 1.10...
  testing automake-1.11... found 1.11.3
checking for libtool >= 1.5...
  testing libtoolize... found 2.4.2
checking for intltool >= 0.30...
  testing intltoolize... found 0.50.2
checking for pkg-config >= 0.14.0...
  testing pkg-config... found 0.26
checking for gtk-doc >= 1.0...
  testing gtkdocize... found 1.18
Checking for required M4 macros...
Checking for forbidden M4 macros...
Processing ./configure.ac
Running libtoolize...
libtoolize: putting auxiliary files in `.'.
libtoolize: copying file `./ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
libtoolize: copying file `m4/libtool.m4'
libtoolize: copying file `m4/ltoptions.m4'
libtoolize: copying file `m4/ltsugar.m4'
libtoolize: copying file `m4/ltversion.m4'
libtoolize: copying file `m4/lt~obsolete.m4'
Running intltoolize...
Running gtkdocize...
Running aclocal-1.11...
Running autoconf...
Running autoheader...
Running automake-1.11...
Running ./configure --enable-gtk-doc --enable-debug ...
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking dependency style of gcc... gcc3
checking for library containing strerror... none required
checking for gcc... (cached) gcc
checking whether we are using the GNU C compiler... (cached) yes
checking whether gcc accepts -g... (cached) yes
checking for gcc option to accept ISO C89... (cached) none needed
checking dependency style of gcc... (cached) gcc3
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking how to print strings... printf
checking for a sed that does not truncate output... /bin/sed
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for fgrep... /bin/grep -F
checking for ld used by gcc... /usr/bin/ld
checking if the linker (/usr/bin/ld) is GNU ld... yes
checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B
checking the name lister (/usr/bin/nm -B) interface... BSD nm
checking whether ln -s works... yes
checking the maximum length of command line arguments... 1572864
checking whether the shell understands some XSI constructs... yes
checking whether the shell understands "+="... yes
checking how to con...

Revision history for this message
Facundo Batista (facundo) wrote :

Tested again latest changes.

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (246.2 KiB)

The attempt to merge lp:~verterok/ubuntuone-client/fix-987376 into lp:ubuntuone-client failed. Below is the output from the failed tests.

/usr/bin/gnome-autogen.sh
checking for autoconf >= 2.53...
  testing autoconf2.50... not found.
  testing autoconf... found 2.68
checking for automake >= 1.10...
  testing automake-1.11... found 1.11.3
checking for libtool >= 1.5...
  testing libtoolize... found 2.4.2
checking for intltool >= 0.30...
  testing intltoolize... found 0.50.2
checking for pkg-config >= 0.14.0...
  testing pkg-config... found 0.26
checking for gtk-doc >= 1.0...
  testing gtkdocize... found 1.18
Checking for required M4 macros...
Checking for forbidden M4 macros...
Processing ./configure.ac
Running libtoolize...
libtoolize: putting auxiliary files in `.'.
libtoolize: copying file `./ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
libtoolize: copying file `m4/libtool.m4'
libtoolize: copying file `m4/ltoptions.m4'
libtoolize: copying file `m4/ltsugar.m4'
libtoolize: copying file `m4/ltversion.m4'
libtoolize: copying file `m4/lt~obsolete.m4'
Running intltoolize...
Running gtkdocize...
Running aclocal-1.11...
Running autoconf...
Running autoheader...
Running automake-1.11...
Running ./configure --enable-gtk-doc --enable-debug ...
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for style of include used by make... GNU
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking dependency style of gcc... gcc3
checking for library containing strerror... none required
checking for gcc... (cached) gcc
checking whether we are using the GNU C compiler... (cached) yes
checking whether gcc accepts -g... (cached) yes
checking for gcc option to accept ISO C89... (cached) none needed
checking dependency style of gcc... (cached) gcc3
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
checking how to print strings... printf
checking for a sed that does not truncate output... /bin/sed
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for fgrep... /bin/grep -F
checking for ld used by gcc... /usr/bin/ld
checking if the linker (/usr/bin/ld) is GNU ld... yes
checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B
checking the name lister (/usr/bin/nm -B) interface... BSD nm
checking whether ln -s works... yes
checking the maximum length of command line arguments... 1572864
checking whether the shell understands some XSI constructs... yes
checking whether the shell understands "+="... yes
checking how to con...

Unmerged revisions

1234. By Guillermo Gonzalez

fix tests

1233. By Guillermo Gonzalez

fix bad_crc tests to run on windows.

1232. By Guillermo Gonzalez

don't iterate the generator in DataFile.iter_entries, just return it.

1231. By Guillermo Gonzalez

fix max mmaped file limit

1230. By Guillermo Gonzalez

only use mmap in data files <= 2**32, to avoid hitting address space limits, and fallback to standard IO.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'tests/syncdaemon/test_tritcask.py'
--- tests/syncdaemon/test_tritcask.py 2012-04-09 20:07:05 +0000
+++ tests/syncdaemon/test_tritcask.py 2012-05-08 14:03:17 +0000
@@ -205,8 +205,8 @@
205 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))205 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))
206 # write a different value -> random bytes206 # write a different value -> random bytes
207 # now write some garbage to the end of file207 # now write some garbage to the end of file
208 db.live_file.fd.write(os.urandom(100))208 db.live_file.fd.seek(db.live_file.fd.tell()-3)
209 db.live_file.fd.flush()209 db.live_file.fd.write(os.urandom(3))
210 # and add 10 new entries210 # and add 10 new entries
211 for i in range(10, 20):211 for i in range(10, 20):
212 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))212 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))
@@ -290,7 +290,7 @@
290 with contextlib.closing(fmap):290 with contextlib.closing(fmap):
291 current_pos = 0291 current_pos = 0
292 (crc32, tstamp, key_sz, value_sz, row_type,292 (crc32, tstamp, key_sz, value_sz, row_type,
293 key, value, pos), new_pos = data_file.read(fmap, current_pos)293 key, value, pos), new_pos = data_file.read(fmap)
294 current_pos = new_pos294 current_pos = new_pos
295 self.assertEqual(crc32_size+header_size+key_sz+value_sz, new_pos)295 self.assertEqual(crc32_size+header_size+key_sz+value_sz, new_pos)
296 self.assertEqual(orig_tstamp, tstamp)296 self.assertEqual(orig_tstamp, tstamp)
@@ -299,7 +299,7 @@
299 self.assertEqual('bar', value)299 self.assertEqual('bar', value)
300 self.assertEqual(0, row_type)300 self.assertEqual(0, row_type)
301 (crc32, tstamp, key_sz, value_sz, row_type,301 (crc32, tstamp, key_sz, value_sz, row_type,
302 key, value, pos), new_pos = data_file.read(fmap, current_pos)302 key, value, pos), new_pos = data_file.read(fmap)
303 self.assertEqual(303 self.assertEqual(
304 crc32_size+header_size+key_sz+value_sz+current_pos, new_pos)304 crc32_size+header_size+key_sz+value_sz+current_pos, new_pos)
305 self.assertEqual(tstamp1, tstamp)305 self.assertEqual(tstamp1, tstamp)
@@ -323,7 +323,7 @@
323 fd.flush()323 fd.flush()
324 fmap = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)324 fmap = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)
325 with contextlib.closing(fmap):325 with contextlib.closing(fmap):
326 self.assertRaises(BadCrc, data_file.read, fmap, 0)326 self.assertRaises(BadCrc, data_file.read, fmap)
327327
328 def test_read_bad_header(self):328 def test_read_bad_header(self):
329 """Test for read method with a bad header/unpack error."""329 """Test for read method with a bad header/unpack error."""
@@ -340,7 +340,14 @@
340 fd.flush()340 fd.flush()
341 fmap = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)341 fmap = mmap.mmap(fd.fileno(), 0, access=mmap.ACCESS_READ)
342 with contextlib.closing(fmap):342 with contextlib.closing(fmap):
343 self.assertRaises(BadHeader, data_file.read, fmap, 0)343 self.assertRaises(BadHeader, data_file.read, fmap)
344
345
346class NoMmapDataFileTest(DataFileTest):
347
348 def setUp(self):
349 self.patch(DataFile, 'max_mmap_size', 1)
350 return super(NoMmapDataFileTest, self).setUp()
344351
345352
346class TempDataFileTest(DataFileTest):353class TempDataFileTest(DataFileTest):
@@ -466,10 +473,9 @@
466 db = Tritcask(self.base_dir)473 db = Tritcask(self.base_dir)
467 for i in range(10):474 for i in range(10):
468 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))475 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))
469 # write a different value -> random bytes476 # write a different value to the last item.
470 # now write some garbage to the end of file477 db.live_file.fd.seek(db.live_file.fd.tell()-3)
471 db.live_file.fd.write(os.urandom(100))478 db.live_file.fd.write(os.urandom(3))
472 db.live_file.fd.flush()
473 # and add 10 new entries479 # and add 10 new entries
474 for i in range(10, 20):480 for i in range(10, 20):
475 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))481 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))
@@ -567,6 +573,25 @@
567 self.assertEqual(data_file.hint_size, len("some data"))573 self.assertEqual(data_file.hint_size, len("some data"))
568574
569575
576class NoMmapImmutableDataFileTest(ImmutableDataFileTest):
577
578 def setUp(self):
579 self.patch(ImmutableDataFile, 'max_mmap_size', 1)
580 return super(NoMmapImmutableDataFileTest, self).setUp()
581
582 def test__open(self):
583 """Test the _open private method."""
584 new_file = DataFile(self.base_dir)
585 # write some data
586 new_file.fd.write('foo')
587 immutable_file = new_file.make_immutable()
588 self.assertTrue(immutable_file.fd is not None)
589 self.assertTrue(immutable_file.fmmap is None)
590 # check that the file is opened only for read
591 self.assertRaises(IOError, immutable_file.fd.write, 'foo')
592 immutable_file.close()
593
594
570class DeadDataFileTest(ImmutableDataFileTest):595class DeadDataFileTest(ImmutableDataFileTest):
571 """Tests for DeadDataFile."""596 """Tests for DeadDataFile."""
572597
@@ -1277,9 +1302,10 @@
1277 for i in range(10):1302 for i in range(10):
1278 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))1303 db.put(i, 'foo%d' % (i,), 'bar%s' % (i,))
1279 self.assertFalse(db.should_rotate())1304 self.assertFalse(db.should_rotate())
1280 # write a different value -> random bytes1305 # write a different value to the last item.
1281 # now write some garbage to the end of file1306 # write a different value to the last item.
1282 db.live_file.fd.write(os.urandom(100))1307 db.live_file.fd.seek(db.live_file.fd.tell()-5)
1308 db.live_file.fd.write(os.urandom(5))
1283 db.live_file.fd.flush()1309 db.live_file.fd.flush()
1284 db.shutdown()1310 db.shutdown()
1285 called = []1311 called = []
@@ -1885,3 +1911,4 @@
1885 """Test that the initial value is > 0."""1911 """Test that the initial value is > 0."""
1886 timer = WindowsTimer()1912 timer = WindowsTimer()
1887 self.assertTrue(int(timer.time()) > 0)1913 self.assertTrue(int(timer.time()) > 0)
1914
18881915
=== modified file 'ubuntuone/syncdaemon/tritcask.py'
--- ubuntuone/syncdaemon/tritcask.py 2012-04-09 20:07:05 +0000
+++ ubuntuone/syncdaemon/tritcask.py 2012-05-08 14:03:17 +0000
@@ -74,10 +74,6 @@
74VERSION = 'v1'74VERSION = 'v1'
75FILE_SUFFIX = '.tritcask-%s.data' % VERSION75FILE_SUFFIX = '.tritcask-%s.data' % VERSION
7676
77EXTRA_SEEK = False
78if sys.platform == 'win32':
79 EXTRA_SEEK = True
80
81logger = logging.getLogger('ubuntuone.SyncDaemon.tritcask')77logger = logging.getLogger('ubuntuone.SyncDaemon.tritcask')
8278
8379
@@ -182,6 +178,7 @@
182 """Class that encapsulates data file handling."""178 """Class that encapsulates data file handling."""
183179
184 last_generated_id = 0180 last_generated_id = 0
181 max_mmap_size = int(2**31-1) # cause 2**31 it's a long in 32bits.
185182
186 def __init__(self, base_path, filename=None):183 def __init__(self, base_path, filename=None):
187 """Create a DataFile instance.184 """Create a DataFile instance.
@@ -250,18 +247,23 @@
250 self.fd = None247 self.fd = None
251248
252 def iter_entries(self):249 def iter_entries(self):
253 """Return a generator for the entries in the file."""250 """Return a generator for the entries in the file using mmap."""
254 fmmap = mmap.mmap(self.fd.fileno(), 0, access=mmap.ACCESS_READ)251 if self.size >= self.max_mmap_size:
255 with contextlib.closing(fmmap):252 for entry in self._iter_entries(self.fd):
256 for entry in self._iter_mmaped_entries(fmmap):
257 yield entry253 yield entry
254 else:
255 fmmap = mmap.mmap(self.fd.fileno(), 0, access=mmap.ACCESS_READ)
256 with contextlib.closing(fmmap):
257 for entry in self._iter_entries(fmmap):
258 yield entry
258259
259 def _iter_mmaped_entries(self, fmmap):260 def _iter_entries(self, fd):
260 """Return a generator for the entries in the mmaped file."""261 """Return a generator for the entries in the mmaped file."""
261 current_pos = 0262 current_pos = 0
263 fd.seek(current_pos)
262 while True:264 while True:
263 try:265 try:
264 entry, new_pos = self.read(fmmap, current_pos)266 entry, new_pos = self.read(fd)
265 current_pos = new_pos267 current_pos = new_pos
266 yield entry268 yield entry
267 except EOFError:269 except EOFError:
@@ -294,10 +296,8 @@
294 tstamp = timestamp()296 tstamp = timestamp()
295 header = header_struct.pack(tstamp, key_sz, value_sz, row_type)297 header = header_struct.pack(tstamp, key_sz, value_sz, row_type)
296 crc32 = crc32_struct.pack(zlib.crc32(header + key + value))298 crc32 = crc32_struct.pack(zlib.crc32(header + key + value))
297 if EXTRA_SEEK:299 # always go to the EOF before write, as we aren't using mmap any more.
298 # seek to end of file even if we are in append mode, but py2.x IO300 self.fd.seek(0, os.SEEK_END)
299 # in win32 is really buggy, see: http://bugs.python.org/issue3207
300 self.fd.seek(0, os.SEEK_END)
301 self.fd.write(crc32 + header)301 self.fd.write(crc32 + header)
302 self.fd.write(key)302 self.fd.write(key)
303 value_pos = self.fd.tell()303 value_pos = self.fd.tell()
@@ -305,12 +305,13 @@
305 self.fd.flush()305 self.fd.flush()
306 return tstamp, value_pos, value_sz306 return tstamp, value_pos, value_sz
307307
308 def read(self, fmmap, current_pos):308 def read(self, fd):
309 """Read a single entry from the current position."""309 """Read a single entry from the current position."""
310 crc32_bytes = fmmap[current_pos:current_pos + crc32_size]310 current_pos = fd.tell()
311 current_pos += crc32_size311 data = fd.read(crc32_size+header_size)
312 header = fmmap[current_pos:current_pos + header_size]312 current_pos += crc32_size+header_size
313 current_pos += header_size313 crc32_bytes = data[:crc32_size]
314 header = data[crc32_size:]
314 if header == '' or crc32_bytes == '':315 if header == '' or crc32_bytes == '':
315 # reached EOF316 # reached EOF
316 raise EOFError317 raise EOFError
@@ -319,10 +320,11 @@
319 tstamp, key_sz, value_sz, row_type = header_struct.unpack(header)320 tstamp, key_sz, value_sz, row_type = header_struct.unpack(header)
320 except struct.error, e:321 except struct.error, e:
321 raise BadHeader(e)322 raise BadHeader(e)
322 key = fmmap[current_pos:current_pos + key_sz]323 data = fd.read(key_sz+value_sz)
324 key = data[:key_sz]
323 current_pos += key_sz325 current_pos += key_sz
324 value_pos = current_pos326 value_pos = current_pos
325 value = fmmap[current_pos:current_pos + value_sz]327 value = data[key_sz:]
326 current_pos += value_sz328 current_pos += value_sz
327 # verify the crc32 of the data329 # verify the crc32 of the data
328 if zlib.crc32(header + key + value) == crc32:330 if zlib.crc32(header + key + value) == crc32:
@@ -370,8 +372,9 @@
370372
371 def _open(self):373 def _open(self):
372 self.fd = open(self.filename, 'rb')374 self.fd = open(self.filename, 'rb')
373 fmmap = mmap.mmap(self.fd.fileno(), 0, access=mmap.ACCESS_READ)375 if self.size < self.max_mmap_size:
374 self.fmmap = fmmap376 fmmap = mmap.mmap(self.fd.fileno(), 0, access=mmap.ACCESS_READ)
377 self.fmmap = fmmap
375378
376 def close(self):379 def close(self):
377 """Close the file descriptor and mmap."""380 """Close the file descriptor and mmap."""
@@ -387,13 +390,20 @@
387390
388 def iter_entries(self):391 def iter_entries(self):
389 """Return a generator for the entries in the mmaped file."""392 """Return a generator for the entries in the mmaped file."""
390 for entry in self._iter_mmaped_entries(self.fmmap):393 fd = self.fd
391 yield entry394 if self.fmmap is not None:
395 fd = self.fmmap
396 return self._iter_entries(fd)
392397
393 def __getitem__(self, item):398 def __getitem__(self, item):
394 """__getitem__ to support slicing and *only* slicing."""399 """__getitem__ to support slicing and *only* slicing."""
395 if isinstance(item, slice):400 if isinstance(item, slice):
396 return self.fmmap[item]401 # if we have an mmap, use it.
402 if self.fmmap is not None:
403 return self.fmmap[item]
404 else:
405 self.fd.seek(item.start)
406 return self.fd.read(item.stop - item.start)
397 else:407 else:
398 raise ValueError('Only slice is supported')408 raise ValueError('Only slice is supported')
399409

Subscribers

People subscribed via source and target branches