You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PeerStorage.health() only checks that the watcher reference is non-null — a watcher whose iterator dies silently is never detected, so restartWorthy never fires #67
if the for await loop ends — the native watcher's async iterator returns, throws, or the process otherwise stops iterating (e.g. an OS-level fs-event hiccup, or the Too many open files crash path described in #14) — this.watcherDeno is not reset to undefined. The stale reference keeps health().ok at true indefinitely.
Since Peer.probeHealth() derives restartWorthy from base.ok:
a storage peer that silently stopped watching never reports ok: false, so restartWorthy never becomes true and the container-restart safety net never engages. The bridge keeps running, keeps reporting "watching" in the health file, and simply stops dispatching any local file change until someone manually restarts the container — with no signal that anything is wrong.
The chokidar path (useChokidar: true) has the identical defect, since ok there is also just !!(this.watcher).
Reproduction
Start the bridge with a storage peer (useChokidar: false).
Cause the native watcher's iterator to end without an explicit .close() call (e.g. hit the fd-limit crash in Too many open files (os error 24) #14, or interrupt/kill the process feeding fs events in a way that ends the async iterator but doesn't throw past startDenoFsWatch()'s caller).
Modify a file under the watched path. It is never dispatched.
Check health() / the heartbeat file — it still reports ok: true, "watching".
Restarting the container recovers the file via the startup scanOfflineChanges scan, confirming the watcher (not something downstream) was the dead component.
Too many open files (os error 24) #14 — "Too many open files" crash. One concrete way the watcher's iterator can end; this issue is about health() failing to detect any cause of iterator death, not just this one trigger.
Suggested direction
Track watcher liveness explicitly instead of !!ref — e.g. reset this.watcherDeno/this.watcher to undefined (or flip a dedicated alive flag) as soon as the watch loop/iterator ends for any reason, so health() reflects reality and restartWorthy can actually trip. Optionally pair this with a periodic reconciliation pass (diff the watched tree against a persisted inventory) so a missed event is caught within a bounded interval even before a restart happens, rather than only on next container start.
Body
Summary
PeerStorage.health()reports liveness as:This only checks that the watcher object reference is non-null — not that the watcher is actually still delivering events. In
startDenoFsWatch():if the
for awaitloop ends — the native watcher's async iterator returns, throws, or the process otherwise stops iterating (e.g. an OS-level fs-event hiccup, or theToo many open filescrash path described in #14) —this.watcherDenois not reset toundefined. The stale reference keepshealth().okattrueindefinitely.Since
Peer.probeHealth()derivesrestartWorthyfrombase.ok:a storage peer that silently stopped watching never reports
ok: false, sorestartWorthynever becomestrueand the container-restart safety net never engages. The bridge keeps running, keeps reporting "watching" in the health file, and simply stops dispatching any local file change until someone manually restarts the container — with no signal that anything is wrong.The chokidar path (
useChokidar: true) has the identical defect, sinceokthere is also just!!(this.watcher).Reproduction
useChokidar: false)..close()call (e.g. hit the fd-limit crash in Too many open files (os error 24) #14, or interrupt/kill the process feeding fs events in a way that ends the async iterator but doesn't throw paststartDenoFsWatch()'s caller).health()/ the heartbeat file — it still reportsok: true,"watching".scanOfflineChangesscan, confirming the watcher (not something downstream) was the dead component.Related, but distinct from
scanOfflineChangesnever tombstones files deleted while the bridge was down. Same general area (recovering from a watcher gap) but this issue is about detecting that the watcher itself died in the first place, not about what a recovery scan covers once you know to run one.PeerCouchDB's watch checkpoint (since) never persisted, so restarts silently skip missed remote changes. Same "silently skip changes" theme, but on the CouchDB peer, not the storage peer.health()failing to detect any cause of iterator death, not just this one trigger.Suggested direction
Track watcher liveness explicitly instead of
!!ref— e.g. resetthis.watcherDeno/this.watchertoundefined(or flip a dedicatedaliveflag) as soon as the watch loop/iterator ends for any reason, sohealth()reflects reality andrestartWorthycan actually trip. Optionally pair this with a periodic reconciliation pass (diff the watched tree against a persisted inventory) so a missed event is caught within a bounded interval even before a restart happens, rather than only on next container start.