# This module is part of urllib3 and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-from collections import MutableMapping, deque
+from collections import deque, MutableMapping
+from threading import RLock
__all__ = ['RecentlyUsedContainer']
away the least-recently-used keys beyond ``maxsize``.
"""
- # TODO: Make this threadsafe. _prune_invalidated_entries should be the
- # only real pain-point for this.
-
# If len(self.access_log) exceeds self._maxsize * CLEANUP_FACTOR, then we
# will attempt to cleanup the invalidated entries in the access_log
# datastructure during the next 'get' operation.
# We use a deque to to store our keys ordered by the last access.
self.access_log = deque()
+ self.access_log_lock = RLock()
# We look up the access log entry by the key to invalidate it so we can
# insert a new authorative entry at the head without having to dig and
# Trigger a heap cleanup when we get past this size
self.access_log_limit = maxsize * self.CLEANUP_FACTOR
- def _push_entry(self, key):
- "Push entry onto our access log, invalidate the old entry if exists."
- # Invalidate old entry if it exists
+ def _invalidate_entry(self, key):
+ "If exists: Invalidate old entry and return it."
old_entry = self.access_lookup.get(key)
if old_entry:
old_entry.is_valid = False
- new_entry = AccessEntry(key)
+ return old_entry
+ def _push_entry(self, key):
+ "Push entry onto our access log, invalidate the old entry if exists."
+ self._invalidate_entry(key)
+
+ new_entry = AccessEntry(key)
self.access_lookup[key] = new_entry
+
+ self.access_log_lock.acquire()
self.access_log.appendleft(new_entry)
+ self.access_log_lock.release()
def _prune_entries(self, num):
"Pop entries from our access log until we popped ``num`` valid ones."
while num > 0:
+ self.access_log_lock.acquire()
p = self.access_log.pop()
+ self.access_log_lock.release()
if not p.is_valid:
continue # Invalidated entry, skip
- del self._container[p.key]
- del self.access_lookup[p.key]
+ self._container.pop(p.key, None)
+ self.access_lookup.pop(p.key, None)
num -= 1
def _prune_invalidated_entries(self):
"Rebuild our access_log without the invalidated entries."
+ self.access_log_lock.acquire()
self.access_log = deque(e for e in self.access_log if e.is_valid)
+ self.access_log_lock.release()
def _get_ordered_access_keys(self):
- # Used for testing
- return [e.key for e in self.access_log if e.is_valid]
+ "Return ordered access keys for inspection. Used for testing."
+ self.access_log_lock.acquire()
+ r = [e.key for e in self.access_log if e.is_valid]
+ self.access_log_lock.release()
+
+ return r
def __getitem__(self, key):
item = self._container.get(key)
if not item:
- return
+ raise KeyError(key)
# Insert new entry with new high priority, also implicitly invalidates
# the old entry.
def __delitem__(self, key):
self._invalidate_entry(key)
del self._container[key]
- del self._access_lookup[key]
+ del self.access_lookup[key]
def __len__(self):
return self._container.__len__()