81b27aa9028d
give each remote connection check its own probe key, so two machines setting up at once no longer report a broken remote (3.0.62)
- time
- 2026-09-26 02:48 UTC · 2 weeks ago
- machine
- koh2
- agent
- claude-opus-5-5
- session
- live remote scenarios
- parent
- ef399328b6ef
↓ koh-81b27aa9.tar.gzatlas at this save
files changed4 files
VERSION+1 −1src/remote/folder_test.v+6 −5src/remote/remote.v+10 −8v.mod+1 −1src/remote/folder_test.v+6 −5
@@ -95,11 +95,12 @@9595 }9696 mut backend := new_folder(base)!9797 check(mut backend)!98- assert !backend.get(probe_key)!.found98+ assert backend.list('')!.len == 09999 100- // A check interrupted after its first write leaves the probe behind; the101- // next check must not mistake it for a remote that refuses writes.102- backend.put_if_absent(probe_key, 'left over'.bytes())!100+ // Another machine setting up at the same moment has its own probe on the101+ // remote; this check must neither trip over it nor delete it.102+ backend.put_if_absent(probe_key, 'in flight'.bytes())!103103 check(mut backend)!104- assert !backend.get(probe_key)!.found104+ assert backend.get(probe_key)!.found105+ assert backend.list('')! == [probe_key]105106 }src/remote/remote.v+10 −8
@@ -7,6 +7,8 @@77 88 module remote99 10+import rand11+1012 // get not finding a key is a normal answer, not an error, so `Object.found`1113 // carries it and errors mean real trouble (a refused connection, bad keys).1214 pub struct Object {@@ -35,23 +37,23 @@3537 // delete it. A backend that cannot do that cannot keep two machines honest.3638 pub fn check(mut backend Backend) ! {3739 payload := 'koh probe'.bytes()38- // The probe is Koh's own key; one left by an interrupted check would make39- // the first write below look refused.40- backend.delete(probe_key)!41- if !backend.put_if_absent(probe_key, payload)! {40+ // Each check has its own key: with a shared one, two machines setting up41+ // at once delete each other's probe and report a broken remote.42+ key := '${probe_key}-${rand.hex(16)}'43+ if !backend.put_if_absent(key, payload)! {4244 return error('the remote refused to store a new object')4345 }4446 defer {4547 // Also on the early returns below; deleting twice is harmless.46- backend.delete(probe_key) or {}48+ backend.delete(key) or {}4749 }48- if backend.put_if_absent(probe_key, payload)! {50+ if backend.put_if_absent(key, payload)! {4951 return error('the remote overwrote an existing object — conditional writes are not working')5052 }51- probe := backend.get(probe_key)!53+ probe := backend.get(key)!5254 if !probe.found || probe.body != payload {5355 return error('the remote did not give back what was written')5456 }5557 // Strip needs delete, so a remote that cannot delete fails here.56- backend.delete(probe_key)!58+ backend.delete(key)!5759 }v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.0.61'3+ version: '3.0.62'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }