From f5379ba2825b88aeb2bbd520684bebe2eadb471c Mon Sep 17 00:00:00 2001 From: linkliti Date: Mon, 11 May 2026 21:54:46 +0300 Subject: [PATCH] fix(watchdog): move observer refresh outside registry lock in add/remove/clear Dedent _refresh_observer() calls out of with self._lock: blocks in _WatchRegistry.add(), remove(), and clear() to break an AB-BA deadlock between _WatchRegistry._lock (L1) and BaseObserver._lock (L2) in `/opt/venv-a0/lib/python3.12/site-packages/watchdog/observers/api.py`. The main thread held L1 while calling unschedule_all() which needs L2; the observer dispatch thread held L2 while calling dispatch() which needs L1. --- helpers/watchdog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/helpers/watchdog.py b/helpers/watchdog.py index 8025de419..7f6322cc6 100644 --- a/helpers/watchdog.py +++ b/helpers/watchdog.py @@ -130,8 +130,8 @@ class _WatchRegistry: pending.timer.cancel() self._watches.update(watches) self._watch_ids_by_group[id] = set(watches) - if not self._batching: - self._refresh_observer() + if not self._batching: + self._refresh_observer() def remove(self, id: str) -> bool: with self._lock: @@ -142,9 +142,9 @@ class _WatchRegistry: pending = self._pending_batches.pop(watch_id, None) if pending and pending.timer: pending.timer.cancel() - if removed and not self._batching: - self._refresh_observer() - return removed + if removed and not self._batching: + self._refresh_observer() + return removed def clear(self) -> None: with self._lock: @@ -152,8 +152,8 @@ class _WatchRegistry: self._watch_ids_by_group.clear() pending_batches = list(self._pending_batches.values()) self._pending_batches.clear() - if not self._batching: - self._refresh_observer() + if not self._batching: + self._refresh_observer() for pending in pending_batches: if pending.timer: pending.timer.cancel()