2929THE SOFTWARE.
3030"""
3131
32- import collections .abc as abc
3332import errno
3433import hashlib
3534import logging
@@ -443,125 +442,6 @@ def update_for_frozendict(self, key_hash: Hash, key: Mapping) -> None:
443442# }}}
444443
445444
446- # {{{ lru cache
447-
448- class _LinkedList :
449- """The list operates on nodes of the form [value, leftptr, rightpr]. To create a
450- node of this form you can use `LinkedList.new_node().`
451-
452- Supports inserting at the left and deleting from an arbitrary location.
453- """
454- def __init__ (self ):
455- self .count = 0
456- self .head = None
457- self .end = None
458-
459- @staticmethod
460- def new_node (element ):
461- return [element , None , None ]
462-
463- def __len__ (self ):
464- return self .count
465-
466- def appendleft_node (self , node ):
467- self .count += 1
468-
469- if self .head is None :
470- self .head = self .end = node
471- return
472-
473- self .head [1 ] = node
474- node [2 ] = self .head
475-
476- self .head = node
477-
478- def pop_node (self ):
479- end = self .end
480- self .remove_node (end )
481- return end
482-
483- def remove_node (self , node ):
484- self .count -= 1
485-
486- if self .head is self .end :
487- assert node is self .head
488- self .head = self .end = None
489- return
490-
491- left = node [1 ]
492- right = node [2 ]
493-
494- if left is None :
495- self .head = right
496- else :
497- left [2 ] = right
498-
499- if right is None :
500- self .end = left
501- else :
502- right [1 ] = left
503-
504- node [1 ] = node [2 ] = None
505-
506-
507- class _LRUCache (abc .MutableMapping ):
508- """A mapping that keeps at most *maxsize* items with an LRU replacement policy.
509- """
510- def __init__ (self , maxsize ):
511- self .lru_order = _LinkedList ()
512- self .maxsize = maxsize
513- self .cache = {}
514-
515- def __delitem__ (self , item ):
516- node = self .cache [item ]
517- self .lru_order .remove_node (node )
518- del self .cache [item ]
519-
520- def __getitem__ (self , item ):
521- node = self .cache [item ]
522- self .lru_order .remove_node (node )
523- self .lru_order .appendleft_node (node )
524- # A linked list node contains a tuple of the form (item, value).
525- return node [0 ][1 ]
526-
527- def __contains__ (self , item ):
528- return item in self .cache
529-
530- def __iter__ (self ):
531- return iter (self .cache )
532-
533- def __len__ (self ) -> int :
534- return len (self .cache )
535-
536- def clear (self ):
537- self .cache .clear ()
538- self .lru_order = _LinkedList ()
539-
540- def __setitem__ (self , item , value ):
541- if self .maxsize < 1 :
542- return
543-
544- try :
545- node = self .cache [item ]
546- self .lru_order .remove_node (node )
547- except KeyError :
548- if len (self .lru_order ) >= self .maxsize :
549- # Make room for new elements.
550- end_node = self .lru_order .pop_node ()
551- del self .cache [end_node [0 ][0 ]]
552-
553- node = self .lru_order .new_node ((item , value ))
554- self .cache [item ] = node
555-
556- self .lru_order .appendleft_node (node )
557-
558- assert len (self .cache ) == len (self .lru_order ), \
559- (len (self .cache ), len (self .lru_order ))
560- assert len (self .lru_order ) <= self .maxsize
561-
562- # }}}
563-
564-
565445# {{{ top-level
566446
567447class NoSuchEntryError (KeyError ):
@@ -720,7 +600,7 @@ class WriteOncePersistentDict(_PersistentDictBase):
720600 """A concurrent disk-backed dictionary that disallows overwriting/deletion.
721601
722602 Compared with :class:`PersistentDict`, this class has faster
723- retrieval times.
603+ retrieval times because it uses an LRU cache to cache entries in memory .
724604
725605 .. automethod:: __init__
726606 .. automethod:: __getitem__
@@ -742,14 +622,15 @@ def __init__(self, identifier, key_builder=None, container_dir=None,
742622 """
743623 _PersistentDictBase .__init__ (self , identifier , key_builder , container_dir )
744624 self ._in_mem_cache_size = in_mem_cache_size
745- self .clear_in_mem_cache ()
625+ from functools import lru_cache
626+ self ._fetch = lru_cache (maxsize = in_mem_cache_size )(self ._fetch )
746627
747628 def clear_in_mem_cache (self ) -> None :
748629 """
749630 .. versionadded:: 2023.1.1
750631 """
751632
752- self ._cache = _LRUCache ( self . _in_mem_cache_size )
633+ self ._fetch . cache_clear ( )
753634
754635 def _spin_until_removed (self , lock_file , stacklevel ):
755636 from os .path import exists
@@ -807,19 +688,14 @@ def store(self, key, value, _skip_if_present=False, _stacklevel=0):
807688 def fetch (self , key , _stacklevel = 0 ):
808689 hexdigest_key = self .key_builder (key )
809690
810- # {{{ in memory cache
691+ ( stored_key , stored_value ) = self . _fetch ( hexdigest_key , 1 + _stacklevel )
811692
812- try :
813- stored_key , stored_value = self ._cache [hexdigest_key ]
814- except KeyError :
815- pass
816- else :
817- logger .debug ("%s: in mem cache hit [key=%s]" ,
818- self .identifier , hexdigest_key )
819- self ._collision_check (key , stored_key , 1 + _stacklevel )
820- return stored_value
693+ self ._collision_check (key , stored_key , 1 + _stacklevel )
821694
822- # }}}
695+ return stored_value
696+
697+ def _fetch (self , hexdigest_key , _stacklevel = 0 ): # pylint:disable=method-hidden
698+ # This is separate from fetch() to allow for LRU caching
823699
824700 # {{{ check path exists and is unlocked
825701
@@ -829,7 +705,7 @@ def fetch(self, key, _stacklevel=0):
829705 if not isdir (item_dir ):
830706 logger .debug ("%s: disk cache miss [key=%s]" ,
831707 self .identifier , hexdigest_key )
832- raise NoSuchEntryError (key )
708+ raise NoSuchEntryError (hexdigest_key )
833709
834710 lock_file = self ._lock_file (hexdigest_key )
835711 self ._spin_until_removed (lock_file , 1 + _stacklevel )
@@ -852,9 +728,7 @@ def fetch(self, key, _stacklevel=0):
852728 f"Remove the directory '{ item_dir } ' if necessary. "
853729 f"(caught: { type (e ).__name__ } : { e } )" ,
854730 stacklevel = 1 + _stacklevel )
855- raise NoSuchEntryInvalidKeyError (key )
856-
857- self ._collision_check (key , read_key , 1 + _stacklevel )
731+ raise NoSuchEntryInvalidKeyError (hexdigest_key )
858732
859733 # }}}
860734
@@ -871,16 +745,15 @@ def fetch(self, key, _stacklevel=0):
871745 f"Remove the directory '{ item_dir } ' if necessary."
872746 f"(caught: { type (e ).__name__ } : { e } )" ,
873747 stacklevel = 1 + _stacklevel )
874- raise NoSuchEntryInvalidContentsError (key )
748+ raise NoSuchEntryInvalidContentsError (hexdigest_key )
875749
876750 # }}}
877751
878- self ._cache [hexdigest_key ] = (key , read_contents )
879- return read_contents
752+ return (read_key , read_contents )
880753
881754 def clear (self ):
882755 _PersistentDictBase .clear (self )
883- self ._cache . clear ()
756+ self ._fetch . cache_clear ()
884757
885758
886759class PersistentDict (_PersistentDictBase ):
0 commit comments