1616
1717from threading import Thread
1818from time import perf_counter_ns , sleep
19- from typing import Callable , Generic , ItemsView , KeysView , Optional , TypeVar
19+ from typing import Callable , Generic , List , Optional , Tuple , TypeVar
2020
2121from aws_advanced_python_wrapper .utils .atomic import AtomicInt
2222from aws_advanced_python_wrapper .utils .concurrent import ConcurrentDict
@@ -46,10 +46,10 @@ def __len__(self):
4646 def set_cleanup_interval_ns (self , interval_ns ):
4747 self ._cleanup_interval_ns = interval_ns
4848
49- def keys (self ) -> KeysView :
49+ def keys (self ) -> List [ K ] :
5050 return self ._cdict .keys ()
5151
52- def items (self ) -> ItemsView :
52+ def items (self ) -> List [ Tuple [ K , CacheItem [ V ]]] :
5353 return self ._cdict .items ()
5454
5555 def compute_if_absent (self , key : K , mapping_func : Callable , item_expiration_ns : int ) -> Optional [V ]:
@@ -73,32 +73,28 @@ def _remove_and_dispose(self, key: K):
7373 self ._item_disposal_func (cache_item .item )
7474
7575 def _remove_if_expired (self , key : K ):
76- item = None
77-
7876 def _remove_if_expired_internal (_ , cache_item ):
7977 if self ._should_cleanup_item (cache_item ):
80- nonlocal item
81- item = cache_item .item
78+ # Dispose while holding the lock to prevent race conditions
79+ if self ._item_disposal_func is not None :
80+ self ._item_disposal_func (cache_item .item )
8281 return None
83-
8482 return cache_item
8583
8684 self ._cdict .compute_if_present (key , _remove_if_expired_internal )
8785
88- if item is None or self ._item_disposal_func is None :
89- return
90-
91- self ._item_disposal_func (item )
92-
9386 def _should_cleanup_item (self , cache_item : CacheItem ) -> bool :
9487 if self ._should_dispose_func is not None :
9588 return perf_counter_ns () > cache_item .expiration_time and self ._should_dispose_func (cache_item .item )
9689 return perf_counter_ns () > cache_item .expiration_time
9790
9891 def clear (self ):
99- for _ , cache_item in self ._cdict .items ():
100- if cache_item is not None and self ._item_disposal_func is not None :
101- self ._item_disposal_func (cache_item .item )
92+ # Dispose all items while holding the lock
93+ if self ._item_disposal_func is not None :
94+ self ._cdict .apply_if (
95+ lambda k , v : True , # Apply to all items
96+ lambda k , cache_item : self ._item_disposal_func (cache_item .item )
97+ )
10298 self ._cdict .clear ()
10399
104100 def _cleanup (self ):
@@ -107,7 +103,7 @@ def _cleanup(self):
107103 return
108104
109105 self ._cleanup_time_ns .set (current_time + self ._cleanup_interval_ns )
110- keys = [ key for key , _ in self ._cdict .items ()]
106+ keys = self ._cdict .keys ()
111107 for key in keys :
112108 self ._remove_if_expired (key )
113109
@@ -129,29 +125,21 @@ def compute_if_absent_with_disposal(self, key: K, mapping_func: Callable, item_e
129125 return None if cache_item is None else cache_item .update_expiration (item_expiration_ns ).item
130126
131127 def _remove_if_disposable (self , key : K ):
132- item = None
133-
134128 def _remove_if_disposable_internal (_ , cache_item ):
135129 if self ._should_dispose_func is not None and self ._should_dispose_func (cache_item .item ):
136- nonlocal item
137- item = cache_item .item
130+ if self . _item_disposal_func is not None :
131+ self . _item_disposal_func ( cache_item .item )
138132 return None
139-
140133 return cache_item
141134
142135 self ._cdict .compute_if_present (key , _remove_if_disposable_internal )
143136
144- if item is None or self ._item_disposal_func is None :
145- return
146-
147- self ._item_disposal_func (item )
148-
149137 def _cleanup_thread_internal (self ):
150138 while True :
151139 try :
152140 sleep (self ._cleanup_interval_ns / 1_000_000_000 )
153141 self ._cleanup_time_ns .set (perf_counter_ns () + self ._cleanup_interval_ns )
154- keys = [ key for key , _ in self ._cdict .items ()]
142+ keys = self ._cdict .keys ()
155143 for key in keys :
156144 try :
157145 self ._remove_if_expired (key )
0 commit comments