carlos / platform Public

registry: fingerprint sees db_path rewrites (#69)

Paul Campbell e11fb91526a68eafbb091a427c0fb477f63223b3 parent 5695e88
2 files changed, +30 −6
  • internal/registry/registry.go +10 −6
  • internal/registry/registry_test.go +20 −0
diff --git a/internal/registry/registry.go b/internal/registry/registry.go
index fc5bf7520..8fee34533 100644
--- a/internal/registry/registry.go
+++ b/internal/registry/registry.go
@@ -1097,7 +1097,7 @@ WHERE kind = 'instance' AND next_due > 0 AND next_due <= ? ORDER BY host`, now)
// Fingerprint summarizes the route table over a fresh connection, for the
// edge's cheap change poll. It is content-accurate over the reload-relevant
// columns (status, socket, addr, version, hibernate, channel, backing,
-// tunnel): it hashes the actual values, not a length-weighted aggregate, so
+// tunnel, db_path): it hashes the actual values, not a length-weighted aggregate, so
// two DIFFERENT values of the SAME length (channel canary/1 -> canary/2, a
// git short-sha version bump) change the string exactly like any other
// write. `grants` stays deliberately excluded (registry.go:93's rule: grants
@@ -1113,6 +1113,10 @@ WHERE kind = 'instance' AND next_due > 0 AND next_due <= ? ORDER BY host`, now)
// construction — the old length-weighted aggregate could not see a version
// bump land, so the header would serve stale until an unrelated column
// happened to change too.
+//
+// db_path is hashed too (issue #69): a re-add that only re-points a route's
+// database must reach a running edge, or the activator keeps waking the host
+// on the stale cached path.
func (r *Registry) Fingerprint() (string, error) {
db, err := sql.Open("sqlite", r.path)
if err != nil {
@@ -1123,7 +1127,7 @@ func (r *Registry) Fingerprint() (string, error) {
return "", err
}
rows, err := db.Query(`
-SELECT rowid, status, socket, addr, version, hibernate, channel, backing, tunnel
+SELECT rowid, status, socket, addr, version, hibernate, channel, backing, tunnel, db_path
FROM routes ORDER BY rowid`)
if err != nil {
return "", err
@@ -1133,12 +1137,12 @@ FROM routes ORDER BY rowid`)
n := 0
for rows.Next() {
var rowid, hibernate int64
- var status, socket, addr, version, channel, backing, tunnel string
- if err := rows.Scan(&rowid, &status, &socket, &addr, &version, &hibernate, &channel, &backing, &tunnel); err != nil {
+ var status, socket, addr, version, channel, backing, tunnel, dbPath string
+ if err := rows.Scan(&rowid, &status, &socket, &addr, &version, &hibernate, &channel, &backing, &tunnel, &dbPath); err != nil {
return "", err
}
- fmt.Fprintf(h, "%d\x1f%s\x1f%s\x1f%s\x1f%s\x1f%d\x1f%s\x1f%s\x1f%s\x1e",
- rowid, status, socket, addr, version, hibernate, channel, backing, tunnel)
+ fmt.Fprintf(h, "%d\x1f%s\x1f%s\x1f%s\x1f%s\x1f%d\x1f%s\x1f%s\x1f%s\x1f%s\x1e",
+ rowid, status, socket, addr, version, hibernate, channel, backing, tunnel, dbPath)
n++
}
if err := rows.Err(); err != nil {
diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go
index dc6a24ff2..b571d12de 100644
--- a/internal/registry/registry_test.go
+++ b/internal/registry/registry_test.go
@@ -190,6 +190,26 @@ func TestFingerprintChangesOnWrite(t *testing.T) {
if withTunnel == withBacking {
t.Fatal("fingerprint blind to a tunnel binding")
}
+ // db_path rewrites must reload the edge too: a re-`carlos add` that only
+ // fixes -db otherwise leaves the running activator waking the host with
+ // the stale cached path forever — the exact vitogo incident (issue #69):
+ // `add` reported success, the row was updated, and wakes still died on
+ // the old path minutes later because this poll never moved.
+ rt, err = reg.Get("c.example.com")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rt.DBPath = "/data/somewhere-else.db"
+ if err := reg.Upsert(*rt); err != nil {
+ t.Fatal(err)
+ }
+ withDBPath, err := reg.Fingerprint()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if withDBPath == withTunnel {
+ t.Fatal("fingerprint blind to a db_path rewrite")
+ }
}
func TestSetVersionsAndHibernateAreNarrow(t *testing.T) {