@@ -64,6 +64,9 @@ def clean_func(start, stop):
6464 # Internal threading event used to signal thread termination
6565 _stop_event : threading .Event = field (default_factory = threading .Event )
6666
67+ # Internal lock to protect thread start/stop operations
68+ _lock : threading .Lock = field (default_factory = threading .Lock )
69+
6770 # Internal thread instance
6871 _thread : threading .Thread | None = None
6972
@@ -89,14 +92,15 @@ def start(self) -> None:
8992 automatically terminate when the main process exits (daemon thread).
9093
9194 Note:
92- The thread is started as a daemon thread, meaning it will not prevent
93- the program from exiting if it's still running.
95+ This method is thread-safe. The thread is started as a daemon thread,
96+ meaning it will not prevent the program from exiting if it's still running.
9497 """
95- if self ._thread is not None :
96- return
97- self ._thread = threading .Thread (target = self .loop , daemon = True )
98- self ._thread .start ()
99- self ._debug ("Expiration thread started" )
98+ with self ._lock :
99+ if self ._thread is not None :
100+ return
101+ self ._thread = threading .Thread (target = self .loop , daemon = True )
102+ self ._thread .start ()
103+ self ._debug ("Expiration thread started" )
100104
101105 def stop (self , wait : bool = False ) -> None :
102106 """Stop the expiration thread.
@@ -110,15 +114,17 @@ def stop(self, wait: bool = False) -> None:
110114 False.
111115
112116 Note:
113- When `wait=False`, the thread may continue running briefly after this
114- method returns. Use `wait=True` to ensure the thread has fully stopped
115- before proceeding.
117+ This method is thread-safe. When `wait=False`, the thread may continue
118+ running briefly after this method returns. Use `wait=True` to ensure the
119+ thread has fully stopped before proceeding.
116120 """
117- if self ._thread is None :
118- return
121+ with self ._lock :
122+ if self ._thread is None :
123+ return
124+ thread = self ._thread
119125 self ._stop_event .set ()
120126 if wait :
121- self . _thread .join ()
127+ thread .join ()
122128 self ._debug ("Expiration thread stopped" )
123129 else :
124130 self ._debug ("Stop event set for expiration thread" )
@@ -135,7 +141,8 @@ def loop(self) -> None:
135141 items have been checked)
136142 4. Waits for `delay` seconds before the next iteration
137143
138- The loop terminates when the stop event is set via `stop()`. If
144+ The loop terminates when the stop event is set via `stop()`, or when the
145+ storage is closed (indicated by RuntimeError from the callback). If
139146 `max_checks_per_iteration` is 0, no checks are performed but the loop
140147 continues to wait, allowing the thread to be stopped gracefully.
141148
@@ -146,9 +153,14 @@ def loop(self) -> None:
146153 start_index : int = 0
147154 while not self ._stop_event .is_set ():
148155 if self .max_checks_per_iteration > 0 :
149- tested , deleted = self .clean_callback (
150- start_index , start_index + self .max_checks_per_iteration
151- )
156+ try :
157+ tested , deleted = self .clean_callback (
158+ start_index , start_index + self .max_checks_per_iteration
159+ )
160+ except RuntimeError :
161+ # Storage was closed while we were running - exit gracefully
162+ self ._debug ("Storage closed, expiration thread exiting" )
163+ return
152164 if tested == 0 :
153165 start_index = 0 # restart from the beginning
154166 else :
0 commit comments