Potential data race on WindowsApiEmitter._reader between on_thread_stop() and queue_events()
I noticed a possible unsynchronized access to WindowsApiEmitter._reader in watchdog.observers.read_directory_changes.WindowsApiEmitter.
on_thread_stop() reads and clears _reader while holding self._lock:
def on_thread_stop(self) -> None:
with self._lock:
reader = self._reader
self._reader = None
if reader is not None:
reader.stop()But queue_events() reads _reader without holding the same lock:
def queue_events(self, timeout: float) -> None:
reader = self._reader
if reader is None:
return
...
for winapi_event in reader.get_events(timeout):
...If on_thread_stop() runs concurrently with queue_events(), queue_events() may retain a reference to a DirectoryChangeReader that is being stopped concurrently. This may be intentional/tolerated because the method checks for None, but the read itself is not synchronized with the write in on_thread_stop().
Would it make sense to read _reader under self._lock, similar to on_thread_stop(), e.g. by copying it while holding the lock and then using the local reference outside the lock?
Relevant lines from the current source:
- src/watchdog/observers/read_directory_changes.py: WindowsApiEmitter.on_thread_stop
- src/watchdog/observers/read_directory_changes.py: WindowsApiEmitter.queue_events Why this may matter
The race is probably benign in many cases because queue_events() exits if _reader is None, but a concurrent stop can happen after the unsynchronized read and before/during reader.get_events(timeout). At minimum, this looks like a small synchronization inconsistency around _reader lifecycle management.
Source: gorakhargosh/watchdog