feat(observe): detect missed notifications via sweep comparison

This commit is contained in:
Marc Billow
2026-07-09 18:43:20 -05:00
parent eecbc19dde
commit e6eada2367
2 changed files with 88 additions and 0 deletions
+26
View File
@@ -44,6 +44,7 @@ class ObserveManager:
self._settle_lock = threading.Lock()
self.subscribed_hrefs: set[str] = set()
self._notified: set[str] = set()
self.fallback_hrefs: set[str] = set()
def mark_write_pending(self, href: str, settle_s: float = DEFAULT_SETTLE_S) -> None:
with self._settle_lock:
@@ -119,3 +120,28 @@ class ObserveManager:
self.mode = mode
self.last_mode_change_ts = time.monotonic()
self.last_mode_change_wall = time.time()
def check_sweep_for_misses(self, sweep_resources: dict[str, dict]) -> bool:
"""Compare a safety-net sweep result against the cache for every
subscribed href. A mismatch outside its settle window means
OBSERVE silently missed a change (it only notifies on change, so
any disagreement is a real miss, not a case of "nothing changed
yet"). Returns True if a miss was detected."""
if self.mode != MODE_OBSERVE:
return False
for href in self.subscribed_hrefs:
if href not in sweep_resources or self._is_settling(href):
continue
cached = self.cache.get(href)
if cached is not None and cached != sweep_resources[href]:
self.log.warning(
"observe missed a change on %s (sweep disagrees with cache)",
href,
)
return True
return False
def downgrade_to_poll(self) -> None:
self.fallback_hrefs = set(self.subscribed_hrefs)
self.subscribed_hrefs = set()
self._set_mode(MODE_POLL)
+62
View File
@@ -189,3 +189,65 @@ def test_try_enter_observe_mode_clears_stale_notifications_on_retry():
assert entered is False
assert mgr.mode == 'poll'
assert mgr.subscribed_hrefs == set()
def test_check_sweep_for_misses_false_when_not_in_observe_mode():
mgr = _manager()
assert mgr.check_sweep_for_misses({'/oven/vs/0': {'a': 2}}) is False
def test_check_sweep_for_misses_detects_mismatch():
mgr = _manager()
session = _FakeSession()
href = '/oven/vs/0'
def _notify_during_grace_period():
time.sleep(0.005)
mgr.on_notification(href, cbor2.dumps({'a': 1}))
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
notifier.start()
mgr.try_enter_observe_mode(session, [href], grace_period_s=0.02)
notifier.join()
assert mgr.mode == 'observe'
# Sweep sees a value the cache never got via notify -> observe missed it.
missed = mgr.check_sweep_for_misses({href: {'a': 2}})
assert missed is True
def test_check_sweep_for_misses_ignores_href_during_settle_window():
mgr = _manager()
session = _FakeSession()
href = '/oven/vs/0'
mgr.on_notification(href, cbor2.dumps({'a': 1}))
mgr.try_enter_observe_mode(session, [href], grace_period_s=0.01)
mgr.mark_write_pending(href, settle_s=5.0)
missed = mgr.check_sweep_for_misses({href: {'a': 2}})
assert missed is False
def test_downgrade_to_poll_moves_subscribed_to_fallback():
mgr = _manager()
session = _FakeSession()
href = '/oven/vs/0'
def _notify_during_grace_period():
time.sleep(0.005)
mgr.on_notification(href, cbor2.dumps({'a': 1}))
notifier = threading.Thread(target=_notify_during_grace_period, daemon=True)
notifier.start()
mgr.try_enter_observe_mode(session, [href], grace_period_s=0.02)
notifier.join()
mgr.downgrade_to_poll()
assert mgr.mode == 'poll'
assert mgr.subscribed_hrefs == set()
assert mgr.fallback_hrefs == {href}