mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-09-14 00:55:08 +00:00
fix(http): close the read-auth single-flight window left open before putIfAbsent
signOnce() looked at the cache a second time to catch a leader that had already finished, but it looked too early — before claiming the in-flight slot. The gap between that look and putIfAbsent still spans signerProvider() and an allocation, and a leader caches its token and retires its entry inside it. A straggler in that gap therefore put into a map the leader had just emptied, won the slot, and signed a duplicate. Winning the slot is not proof that nobody signed; only a look from inside it is. Once we hold the entry no one else can be leader, and our successful put observed the map after that leader's removal, which its cache write is ordered before — so a token visible at that point is the last word. Hand it over and stand down. This is pre-existing, not fallout from the dispatcher change: the same test fails on an unmodified origin/main worktree, and aFastSignerStillSharesOneSignature already says the window is "microseconds wide, so one round hits it only now and then" and runs 200 rounds to catch it. It cost a duplicate signature — with a NIP-55 external signer that is a second IPC round trip, and potentially a second prompt, for a burst of images from one gated host. Verified with 8 consecutive runs of the suite (1600 signing rounds), all green, against a baseline that failed inside the first run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TdidqyhRni5L3ft5h8qti5
This commit is contained in:
+14
@@ -129,6 +129,20 @@ class BlossomReadAuthTokenProvider(
|
||||
val fresh = CompletableDeferred<String?>()
|
||||
inFlight.putIfAbsent(host, fresh)?.let { return it }
|
||||
|
||||
// Winning the slot is not proof that nobody signed. The look above only narrows
|
||||
// the gap — a leader that finished between it and this putIfAbsent has already
|
||||
// cached its token AND retired its entry, so this put landed in a map it had
|
||||
// just emptied and we would sign a duplicate. Only a look from *inside* the slot
|
||||
// closes it: no one else can be leader while we hold the entry, and our put
|
||||
// observed the map after that leader's removal, which its cache write is ordered
|
||||
// before. So a token visible here is the last word, and the right move is to hand
|
||||
// it over and stand down rather than sign again.
|
||||
cachedHeader(host)?.let { cached ->
|
||||
inFlight.remove(host, fresh)
|
||||
fresh.complete(cached)
|
||||
return fresh
|
||||
}
|
||||
|
||||
scope
|
||||
.launch {
|
||||
val header =
|
||||
|
||||
Reference in New Issue
Block a user