Browse Source

We forgot to close files which fell out the lrucache

The initializer now takes a dispose function.  lrucache
claims ownership of the items it contains and will dispose
deleted items.  Ownership can naturally be reclaimed by calling
pop() for the item.
Alan Jenkins 10 years ago
parent
commit
e3f671c4fb
3 changed files with 26 additions and 24 deletions
  1. 16 6
      attic/lrucache.py
  2. 7 9
      attic/repository.py
  3. 3 9
      attic/testsuite/lrucache.py

+ 16 - 6
attic/lrucache.py

@@ -1,15 +1,16 @@
 class LRUCache(dict):
 class LRUCache(dict):
 
 
-    def __init__(self, capacity):
+    def __init__(self, capacity, dispose):
         super(LRUCache, self).__init__()
         super(LRUCache, self).__init__()
         self._lru = []
         self._lru = []
         self._capacity = capacity
         self._capacity = capacity
+        self._dispose = dispose
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
-        try:
-            self._lru.remove(key)
-        except ValueError:
-            pass
+        assert key not in self, (
+            "Unexpected attempt to replace a cached item."
+            " If this is intended, please delete or pop the old item first."
+            " The dispose function will be called on delete (but not pop).")
         self._lru.append(key)
         self._lru.append(key)
         while len(self._lru) > self._capacity:
         while len(self._lru) > self._capacity:
             del self[self._lru[0]]
             del self[self._lru[0]]
@@ -28,7 +29,11 @@ class LRUCache(dict):
             self._lru.remove(key)
             self._lru.remove(key)
         except ValueError:
         except ValueError:
             pass
             pass
-        return super(LRUCache, self).__delitem__(key)
+        error = KeyError(key)
+        removed = super(LRUCache, self).pop(key, error)
+        if removed == error:
+            raise error
+        self._dispose(removed)
 
 
     def pop(self, key, default=None):
     def pop(self, key, default=None):
         try:
         try:
@@ -37,6 +42,11 @@ class LRUCache(dict):
             pass
             pass
         return super(LRUCache, self).pop(key, default)
         return super(LRUCache, self).pop(key, default)
 
 
+    def clear(self):
+        for value in self.values():
+            self._dispose(value)
+        super(LRUCache, self).clear()
+
     def _not_implemented(self, *args, **kw):
     def _not_implemented(self, *args, **kw):
         raise NotImplementedError
         raise NotImplementedError
     popitem = setdefault = update = _not_implemented
     popitem = setdefault = update = _not_implemented

+ 7 - 9
attic/repository.py

@@ -393,7 +393,8 @@ class LoggedIO(object):
 
 
     def __init__(self, path, limit, segments_per_dir, capacity=90):
     def __init__(self, path, limit, segments_per_dir, capacity=90):
         self.path = path
         self.path = path
-        self.fds = LRUCache(capacity)
+        self.fds = LRUCache(capacity,
+                            dispose=lambda fd: fd.close())
         self.segment = 0
         self.segment = 0
         self.limit = limit
         self.limit = limit
         self.segments_per_dir = segments_per_dir
         self.segments_per_dir = segments_per_dir
@@ -401,9 +402,8 @@ class LoggedIO(object):
         self._write_fd = None
         self._write_fd = None
 
 
     def close(self):
     def close(self):
-        for segment in list(self.fds.keys()):
-            self.fds.pop(segment).close()
         self.close_segment()
         self.close_segment()
+        self.fds.clear()
         self.fds = None  # Just to make sure we're disabled
         self.fds = None  # Just to make sure we're disabled
 
 
     def segment_iterator(self, reverse=False):
     def segment_iterator(self, reverse=False):
@@ -477,9 +477,8 @@ class LoggedIO(object):
             return fd
             return fd
 
 
     def delete_segment(self, segment):
     def delete_segment(self, segment):
-        fd = self.fds.pop(segment)
-        if fd is not None:
-            fd.close()
+        if segment in self.fds:
+            del self.fds[segment]
         try:
         try:
             os.unlink(self.segment_filename(segment))
             os.unlink(self.segment_filename(segment))
         except OSError:
         except OSError:
@@ -515,9 +514,8 @@ class LoggedIO(object):
             header = fd.read(self.header_fmt.size)
             header = fd.read(self.header_fmt.size)
 
 
     def recover_segment(self, segment, filename):
     def recover_segment(self, segment, filename):
-        fd = self.fds.pop(segment)
-        if fd is not None:
-            fd.close()
+        if segment in self.fds:
+            del self.fds[segment]
         # FIXME: save a copy of the original file
         # FIXME: save a copy of the original file
         with open(filename, 'rb') as fd:
         with open(filename, 'rb') as fd:
             data = memoryview(fd.read())
             data = memoryview(fd.read())

+ 3 - 9
attic/testsuite/lrucache.py

@@ -5,7 +5,7 @@ from attic.testsuite import AtticTestCase
 class LRUCacheTestCase(AtticTestCase):
 class LRUCacheTestCase(AtticTestCase):
 
 
     def test(self):
     def test(self):
-        c = LRUCache(2)
+        c = LRUCache(2, dispose=lambda _: None)
         self.assert_equal(len(c), 0)
         self.assert_equal(len(c), 0)
         for i, x in enumerate('abc'):
         for i, x in enumerate('abc'):
             c[x] = i
             c[x] = i
@@ -21,19 +21,13 @@ class LRUCacheTestCase(AtticTestCase):
         self.assert_equal(len(c), 2)
         self.assert_equal(len(c), 2)
         self.assert_equal(c['c'], 2)
         self.assert_equal(c['c'], 2)
         self.assert_equal(c['d'], 3)
         self.assert_equal(c['d'], 3)
-        c['c'] = 22
-        c['e'] = 4
-        self.assert_equal(len(c), 2)
-        self.assert_raises(KeyError, lambda: c['d'])
-        self.assert_equal(c['c'], 22)
-        self.assert_equal(c['e'], 4)
         del c['c']
         del c['c']
         self.assert_equal(len(c), 1)
         self.assert_equal(len(c), 1)
         self.assert_raises(KeyError, lambda: c['c'])
         self.assert_raises(KeyError, lambda: c['c'])
-        self.assert_equal(c['e'], 4)
+        self.assert_equal(c['d'], 3)
 
 
     def test_pop(self):
     def test_pop(self):
-        c = LRUCache(2)
+        c = LRUCache(2, dispose=lambda _: None)
         c[1] = 1
         c[1] = 1
         c[2] = 2
         c[2] = 2
         c.pop(1)
         c.pop(1)