Problem
ISOProber._stats is protected by _stats_lock and mutated from async coroutines. Scheduler._health_lock protects cross-thread reads of _health_snapshot. Neither lock has a contention test verifying correctness under concurrent access. Under Python 3.13+ free-threaded builds, data races would be exposed if the lock discipline is incomplete. Additionally, run_cycle() does a _reset_stats() followed by many _bump_stat() calls — there is no test asserting consistent snapshots between operations.
Acceptance Criteria
Implementation Notes
- New test file:
tests/test_thread_safety.py
- ISOProber tests: spawn N threads calling
_bump_stat("miss"), assert count == N
- Scheduler tests: concurrent
health_snapshot() + _publish_health_snapshot() assertions
References
src/paperscout/sources.py (_stats_lock, _bump_stat, _reset_stats)
src/paperscout/monitor.py (_health_lock, _publish_health_snapshot)
Problem
ISOProber._statsis protected by_stats_lockand mutated from async coroutines.Scheduler._health_lockprotects cross-thread reads of_health_snapshot. Neither lock has a contention test verifying correctness under concurrent access. Under Python 3.13+ free-threaded builds, data races would be exposed if the lock discipline is incomplete. Additionally,run_cycle()does a_reset_stats()followed by many_bump_stat()calls — there is no test asserting consistent snapshots between operations.Acceptance Criteria
_bump_stat()calls produce correct totalssnapshot_stats()concurrent with_reset_stats()returns a consistent statehealth_snapshot()from a non-event-loop thread returns consistent dataImplementation Notes
tests/test_thread_safety.py_bump_stat("miss"), assert count == Nhealth_snapshot()+_publish_health_snapshot()assertionsReferences
src/paperscout/sources.py(_stats_lock,_bump_stat,_reset_stats)src/paperscout/monitor.py(_health_lock,_publish_health_snapshot)