From ae2e971dd9f12df69156a915be702fefb9745b84 Mon Sep 17 00:00:00 2001 From: davotoula Date: Sat, 12 Sep 2026 22:29:04 +0200 Subject: [PATCH] fix(blossom): re-check the token cache after winning the in-flight slot The pre-insert cache look added in 6dc631e85d narrowed the single-flight gap but didn't close it: a fast leader can insert, sign, cache and retire its entry entirely between a straggler's cache read and its putIfAbsent, so the straggler wins an empty map and signs a second time. That is the intermittent `expected:<1> but was:<2>` in aFastSignerStillSharesOneSignature failing main CI. Check the cache again once this caller owns the slot. A leader always caches before retiring its entry, so any token minted before the insert is visible there; take it and give the slot back instead of re-signing. Reproduced locally at round 2431 of 5000; two 5000-round runs pass with the fix. --- .../service/http/BlossomReadAuthTokenProvider.kt | 11 +++++++++++ 1 file changed, 11 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..2f75de6084 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,17 @@ class BlossomReadAuthTokenProvider( val fresh = CompletableDeferred() inFlight.putIfAbsent(host, fresh)?.let { return it } + // Third look, now that this caller owns the slot. The look above still leaves a + // gap: a leader can insert, sign, cache and retire its entry entirely between + // that read and the putIfAbsent, so the map is empty again and this caller wins + // it. Any leader that retired before this insert cached first, so a token + // present now is theirs — take it and give the slot back instead of re-signing. + cachedHeader(host)?.let { + inFlight.remove(host, fresh) + fresh.complete(it) + return fresh + } + scope .launch { val header =