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
4 files VERSION+1 −1src/remote/folder_test.v+6 −5src/remote/remote.v+10 −8v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.0.611+3.0.62

view file

src/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 }

view file

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 }

view file

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 }

view file