eleven / messenger Public

router: registry watcher — fingerprint poll instead of PRAGMA data_version (#767)

data_version proved blind to cross-process commits on the production box
(modernc/sqlite WAL-visibility quirk under the router's long-lived
connections) — the watcher sat silent while the tunnel gateway inserted
routes, and only a SIGHUP made them serve. Ordinary SELECTs on the same
handle do see fresh rows, so poll a cheap fingerprint of the routes table
(count, max rowid, a status/socket-sensitive sum) every two seconds and
reload when it moves.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 8be0d48b1fe0b676b6924364e1114af9e22ae7b8 parent 5f5608b
1 file changed, +18 −11
  • router.go +18 −11
diff --git a/router.go b/router.go
index 3f797c0..8b7ae69 100644
--- a/router.go
+++ b/router.go
@@ -313,25 +313,32 @@ func runRouter(args []string) {
}
// Watch the registry for writes from other processes (the tunnel gateway
- // publishes trial routes this way, without needing rights to signal us).
- // SQLite's data_version increments on any commit by another connection, so
- // this is one cheap PRAGMA every couple of seconds — the route map itself
- // is only rebuilt when something actually changed.
+ // publishes routes this way, without needing rights to signal us). We poll
+ // a cheap fingerprint of the routes table rather than PRAGMA data_version:
+ // the pragma proved blind to cross-process commits on the production box
+ // (a modernc/sqlite WAL-visibility quirk under the router's long-lived
+ // connections), while ordinary SELECTs on this same handle demonstrably do
+ // see fresh rows — reload()'s own queries are the proof. The fingerprint
+ // moves on any insert, delete, or status/socket change; scanning a few
+ // dozen rows every two seconds is noise.
go func() {
- var last int64 = -1
+ var last string
+ first := true
for range time.Tick(2 * time.Second) {
- var v int64
- if err := reg.sql.QueryRow("PRAGMA data_version").Scan(&v); err != nil {
+ var fp string
+ err := reg.sql.QueryRow(`SELECT COUNT(*) || ':' || COALESCE(MAX(rowid),0) || ':' ||
+ COALESCE(SUM(rowid*17 + LENGTH(status)*3 + LENGTH(socket)),0) FROM routes`).Scan(&fp)
+ if err != nil {
continue
}
- if last == -1 {
- last = v
+ if first {
+ last, first = fp, false
continue
}
- if v == last {
+ if fp == last {
continue
}
- last = v
+ last = fp
if err := rt.reload(); err != nil {
log.Printf("router: registry-watch reload failed: %v", err)
continue