@@ -942,8 +942,172 @@ def load(self, key, default=None):
942
942
return default
943
943
else :
944
944
return value
945
+
946
+
947
+ def identity (value ):
948
+ return value
949
+
950
+ def good (value ):
951
+ return True
952
+
953
+
954
+ class DecayingLRUCache (object ):
955
+
956
+ """Auto-LRU cache with support for stale entry detection
957
+
958
+ This class implements a variant of `AutoLRUCache`, which
959
+ also supports the detection of entries, that are too old
960
+ and need to be reloaded even if they are are present.
961
+
962
+ The client application needs to provide the following
963
+ parameters:
964
+
965
+ - `loader`
966
+ A callable, which is applied to a key value in order
967
+ to load the associated object. This must be a function
968
+ with the signature `lambda key: ...`
969
+
970
+ - `tester`
971
+ Is applied to a cached element before that is handed
972
+ out to the caller; if this function returns false, the
973
+ cached element is considered "too old" and dropped from
974
+ the cache.
975
+
976
+ - `key`
977
+ A callable, which is applied to a key value in order
978
+ to produce a properly hashable value from it. The
979
+ default key extraction function is `identity`, i.e.,
980
+ we use the supplied key value unchanged.
981
+
982
+ - `capacity`
983
+ Maximum number of elements in kept in the LRU cache. The
984
+ cache starts evicting elements, which have not been
985
+ recently accessed, if the number of preserved elements
986
+ reaches this limit.
987
+
988
+ Note, that eviction is *not* in any way controlled by
989
+ the `tester` function, but by access order only!
990
+ """
991
+
992
+ __slots__ = (
993
+ '__weakref__' ,
994
+ '_DecayingLRUCache__lock' ,
995
+ '_DecayingLRUCache__cache' ,
996
+ '_DecayingLRUCache__loader' ,
997
+ '_DecayingLRUCache__loading' ,
998
+ '_DecayingLRUCache__tester' ,
999
+ '_DecayingLRUCache__key' ,
1000
+ )
1001
+
1002
+ def __init__ (self , loader , tester = good , key = identity , capacity = 1024 ):
1003
+
1004
+ """Initialize a new instance
1005
+
1006
+ """
1007
+
1008
+ super (CarefulLRUCache , self ).__init__ ()
1009
+ self .__lock = Lock ()
1010
+ self .__cache = LRUDict (capacity )
1011
+ self .__loader = loader
1012
+ self .__loading = dict ()
1013
+ self .__tester = tester
1014
+ self .__key = key
1015
+
1016
+ def clear (self , discard_loads = False ):
945
1017
1018
+ """Remove all cached values
946
1019
947
- # TODO: The above class can greatly be simplified, if Twisted is
948
- # available and used. Provide a separate implementation using Twisted
949
- # and its Deferreds.
1020
+ This method removes all values from this cache. If
1021
+ `discard_loads`, then the method also forces all currently
1022
+ running load operations to fail with a `CacheAbandonedError`.
1023
+ Note, that this method has no way of interrupting load
1024
+ operations, so all pending operations will have to complete
1025
+ before the discard condition can be detected.
1026
+ """
1027
+
1028
+ with self .__lock :
1029
+ self .__cache .clear ()
1030
+ if discard_loads :
1031
+ conditions = list ()
1032
+ keys = tuple (self .__loading .iterkeys ())
1033
+ for k in keys :
1034
+ placeholder = self .__loading .pop (k )
1035
+ if placeholder ._state is loading :
1036
+ placeholder ._state = discarded
1037
+ conditions .append (placeholder ._condition )
1038
+ while conditions :
1039
+ conditions .pop ().notifyAll ()
1040
+
1041
+ def load (self , key ):
1042
+
1043
+ """Load a value
1044
+
1045
+ Returns the value associated with `key`. If no
1046
+ matching value is currently present in this cache,
1047
+ or if the value present is considered "too old"
1048
+ by the `tester` function, then a value is loaded via
1049
+ the cache's `loader` function.
1050
+ """
1051
+
1052
+ loader , tester , keyfn = self .__loader , self .__tester , self .__key
1053
+ kref = keyfn (key )
1054
+
1055
+ with self .__lock :
1056
+ item = self .__cache .get (kref , missing )
1057
+ if item is not missing :
1058
+ if tester (item ):
1059
+ return item
1060
+ else :
1061
+ del self .__cache [kref ]
1062
+ placeholder = self .__loading .get (kref )
1063
+ if placeholder is not None :
1064
+ while placeholder ._state is loading :
1065
+ placeholder ._condition .wait ()
1066
+ if placeholder ._state is failed :
1067
+ raise CacheLoadError (key , placeholder ._value )
1068
+ else :
1069
+ if placeholder ._state is available :
1070
+ return placeholder ._value
1071
+ else :
1072
+ assert placeholder ._state is discarded
1073
+ raise CacheAbandonedError (key = key )
1074
+ assert False , "this line should never be reached"
1075
+ else :
1076
+ placeholder = Placeholder (self .__lock )
1077
+ self .__loading [kref ] = placeholder
1078
+ # The previous line was the point of no return.
1079
+ # Reaching this point means, that we are the thread
1080
+ # which has to do the actual loading. It also means,
1081
+ # that we must never leave this method without properly
1082
+ # cleaning up behind us.
1083
+ try :
1084
+ value = loader (key )
1085
+ except :
1086
+ with self .__lock :
1087
+ if placeholder ._state is loading :
1088
+ # We are still responsible for the placeholder.
1089
+ del self .__loading [kref ]
1090
+ placeholder ._value = sys .exc_info ()
1091
+ placeholder ._state = failed
1092
+ placeholder ._condition .notifyAll ()
1093
+ raise CacheLoadError (key , placeholder ._value )
1094
+ else :
1095
+ # Do not notify the condition variable, since
1096
+ # that should already have been done by whoever
1097
+ # changed the placeholder's state
1098
+ raise CacheAbandonedError (key = key , exc_info = sys .exc_info ())
1099
+ else :
1100
+ with self .__lock :
1101
+ if placeholder ._state is loading :
1102
+ # We are still responsible for the placeholder.
1103
+ del self .__loading [kref ]
1104
+ placeholder ._value = value
1105
+ placeholder ._state = available
1106
+ self .__cache [kref ] = value
1107
+ placeholder ._condition .notifyAll ()
1108
+ else :
1109
+ # Do not notify the condition variable, since
1110
+ # that should already have been done by whoever
1111
+ # changed the placeholder's state
1112
+ raise CacheAbandonedError (key = key , value = value )
1113
+ return value
0 commit comments