1+ import atexit
12import threading
23from collections .abc import Generator
34from typing import Optional
@@ -12,6 +13,16 @@ class FileSystemWatcher:
1213 watchfiles is an optional dependency of pyfileindex: importing this
1314 module never requires it, only start() does.
1415
16+ Callers that hold a watcher alive for the lifetime of the process (e.g.
17+ via a path-keyed singleton cache) may never trigger its __del__ during
18+ normal execution -- it would only run during interpreter shutdown, by
19+ which point CPython may already be tearing down the modules and C-API
20+ state that the background thread's native extension depends on, which
21+ can crash the process instead of cleanly joining the thread. start()
22+ therefore registers stop() with atexit, so every watcher is joined
23+ while the interpreter is still fully intact, before that teardown
24+ begins.
25+
1526 Args:
1627 path (str): file system path to watch
1728 """
@@ -53,12 +64,14 @@ def start(self) -> None:
5364 next (self ._generator )
5465 self ._thread = threading .Thread (target = self ._worker , daemon = True )
5566 self ._thread .start ()
67+ atexit .register (self .stop )
5668
5769 def stop (self ) -> None :
5870 """
5971 Stop the background file system watcher. Safe to call even if no
6072 watcher is running.
6173 """
74+ atexit .unregister (self .stop )
6275 if self ._stop_event is not None :
6376 self ._stop_event .set ()
6477 if self ._thread is not None :
0 commit comments