From 88ae3d0f5ac36bc2b1eaaaf00214659da439c118 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 20:20:30 +0000 Subject: [PATCH] fix: close the fast-signer single-flight race in BlossomReadAuthTokenProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `aFastSignerStillSharesOneSignature` failed on a loaded machine (rounds 11, 18 and 31 across three runs, on the branch head and on the already-pushed commit alike): a straggler could miss the in-flight map, miss the cache, get descheduled, and then win `putIfAbsent` only because the fast leader had already cached *and* retired its entry — and sign a second time. Take one more look at the cache after winning the in-flight slot. A prior leader's cache write happens before its `remove`, and winning the slot after that `remove` goes through the same ConcurrentHashMap bin, so the just-minted token is visible there; hand it out and retire the slot instead of launching a duplicate signature. The test class now passes four runs in a row where it previously failed three in a row. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01N56KzPSYiN5edMRamvKEgD --- .../service/http/BlossomReadAuthTokenProvider.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/http/BlossomReadAuthTokenProvider.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/http/BlossomReadAuthTokenProvider.kt index 7c635ce442..94c0323ae9 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/http/BlossomReadAuthTokenProvider.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/http/BlossomReadAuthTokenProvider.kt @@ -129,6 +129,20 @@ class BlossomReadAuthTokenProvider( val fresh = CompletableDeferred() inFlight.putIfAbsent(host, fresh)?.let { return it } + // Third look, now that this caller holds the [inFlight] slot. The second look + // above still leaves a window: a straggler can read the cache before a fast + // leader stores its token, get descheduled, and then win `putIfAbsent` only + // because that leader has since cached *and* retired its entry — and sign a + // second time. Winning the slot after the leader's `remove` means the + // leader's earlier cache write is visible here (both go through the same + // ConcurrentHashMap bin), so an entry now is the just-minted token: hand it + // out and retire the slot instead of launching a duplicate signature. + cachedHeader(host)?.let { + inFlight.remove(host, fresh) + fresh.complete(it) + return fresh + } + scope .launch { val header =