Summary
src/api/scheduler.py uses a shared mutable dict _sync_failures across threads (background scheduler jobs run in separate threads via APScheduler). Reads of _sync_failures[key] at line 57 occur outside the lock, creating a race condition with concurrent writes.
Location
src/api/scheduler.py:52-66: _track_failure() writes under lock but reads at line 57 (count = _sync_failures[key]) happen without holding _sync_failures_lock.
- Other read sites:
_reset_failure() (line 80, pop), _cleanup_mirrors (lines 301-303), _check_community_budgets (lines 361-379).
Fix
Wrap all reads of _sync_failures under the same lock used for writes:
with _sync_failures_lock:
count = _sync_failures.get(key, 0)
Acceptance Criteria
Summary
src/api/scheduler.pyuses a shared mutable dict_sync_failuresacross threads (background scheduler jobs run in separate threads via APScheduler). Reads of_sync_failures[key]at line 57 occur outside the lock, creating a race condition with concurrent writes.Location
src/api/scheduler.py:52-66:_track_failure()writes under lock but reads at line 57 (count = _sync_failures[key]) happen without holding_sync_failures_lock._reset_failure()(line 80, pop),_cleanup_mirrors(lines 301-303),_check_community_budgets(lines 361-379).Fix
Wrap all reads of
_sync_failuresunder the same lock used for writes:Acceptance Criteria
_sync_failuresare protected by_sync_failures_lock