[BUG] AccessTokenCache/SimpleTokenCache: concurrent callers wait forever when the caller refreshing the token is cancelled
Los mantenedores suelen responder en 1 día
@alzimmermsft ya está trabajando en esto.
Desde el 18/10/2026.
- #50856 de @prasanna164-code — fusionado
Evaluación
Este issue todavía no se ha evaluado.
Descripción
Describe the bug
When several callers ask AccessTokenCache (or SimpleTokenCache) for a token at the same time and there is no valid cached token, one caller performs the refresh and the others wait on it. If the refreshing caller is cancelled before the refresh completes, the waiting callers never complete. They stay pending until their own subscribers give up, even after later callers have successfully fetched a new token.
BearerTokenAuthenticationPolicy uses AccessTokenCache on the async path, so an async client can hang its in-flight requests when the request that happened to start the token refresh is cancelled. Typical causes are a timeout(...), a block(Duration) that expires, or a disposed subscription such as a client disconnect in a WebFlux app, while a slow token request (for example the first managed identity token) is in progress.
Cause
In AccessTokenCache.retrieveToken the refresh runs inside
Mono.using(() -> wip,
ignored -> tokenRefresh.materialize()
.flatMap(processTokenRefreshResult(sinksOne, now, fallback))
.doOnError(sinksOne::tryEmitError),
w -> w.set(null));
Waiting callers subscribe to sinksOne.asMono(). On cancellation the cleanup only clears wip; sinksOne is never completed, so the waiters never receive a signal. SimpleTokenCache.getToken has the same pattern. The existing test SimpleTokenCacheTests.wipResetsOnCancel checks that wip is cleared, but not that waiting callers complete.
To Reproduce
Through an async pipeline with BearerTokenAuthenticationPolicy: two concurrent requests, a token request that takes 2 seconds, and a 500 ms timeout on the first request only.
AtomicInteger tokenCalls = new AtomicInteger();
TokenCredential credential = request -> Mono.delay(Duration.ofSeconds(2))
.map(ignored -> new AccessToken("token-" + tokenCalls.incrementAndGet(), OffsetDateTime.now().plusHours(1)));
HttpPipeline pipeline = new HttpPipelineBuilder().httpClient(httpClient) // any client returning 200
.policies(new BearerTokenAuthenticationPolicy(credential, "https://example.azure.net/.default"))
.build();
CompletableFuture<Integer> a = pipeline.send(new HttpRequest(HttpMethod.GET, "https://example.azure.net/a"))
.map(HttpResponse::getStatusCode).timeout(Duration.ofMillis(500)).toFuture();
CompletableFuture<Integer> b = pipeline.send(new HttpRequest(HttpMethod.GET, "https://example.azure.net/b"))
.map(HttpResponse::getStatusCode).toFuture();
With azure-core 1.60.0, request A fails with its own TimeoutException and request B never completes. It is still pending after a third request has succeeded:
request A: TimeoutException (its own timeout)
request B: still waiting after 10 seconds
request C status 200, request B done: false, token calls: 1
The same thing using AccessTokenCache directly:
AtomicInteger calls = new AtomicInteger();
TokenCredential credential = request -> Mono.delay(Duration.ofSeconds(1))
.map(ignored -> new AccessToken("token-" + calls.incrementAndGet(), OffsetDateTime.now().plusHours(1)));
AccessTokenCache cache = new AccessTokenCache(credential);
TokenRequestContext context = new TokenRequestContext().addScopes("https://example.azure.net/.default");
Disposable first = cache.getToken(context, false).subscribe(); // starts the refresh
CompletableFuture<AccessToken> second = cache.getToken(context, false).toFuture(); // waits on that refresh
first.dispose(); // refreshing caller cancelled
try {
System.out.println("second caller got " + second.get(5, TimeUnit.SECONDS).getToken());
} catch (TimeoutException e) {
System.out.println("second caller: no token after 5 seconds");
}
System.out.println("new caller got " + cache.getToken(context, false).block(Duration.ofSeconds(5)).getToken());
System.out.println("second caller done: " + second.isDone());
Output with azure-core 1.59.1 and 1.60.0:
second caller: no token after 5 seconds
new caller got token-1
second caller done: false
With random cancellations it is not a corner case. In a local stress run (40 concurrent callers per round, about a third of them cancelled at random times, 40 rounds), about 8% of the callers that were not cancelled never completed on 1.60.0: 87 of 1,053 with AccessTokenCache and 83 of 1,054 with SimpleTokenCache.
Expected behavior
When the refresh a caller is waiting on ends without a token because the refreshing caller was cancelled, the waiting caller should retry the acquisition instead of waiting forever.
Setup
- Library:
com.azure:azure-core1.59.1 and 1.60.0 (reproduced with both), same code on currentmain - Java 25
- Lenguaje dominante
- Java
- Estrellas
- 2.6k
- Forks
- 2.2k
- Merge medio
- 2 d 4 h
- PR fusionados (30 d)
- 231
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de Azure/azure-sdk-for-java
-
mgmt, in track 1 migration guide, call out that file-based authentication method is not recommendedQuizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. AbiertoAzure.Identity
Dificultad 1/5 Menos de una hora Aptitud para principiantes 74/100
Azure/azure-sdk-for-java#48894 ·
Los mantenedores suelen responder en 1 día
-
ManagedIdentityCredentialBuilder.resourceId/clientId/objectId Javadoc says "user-assigned or system-assigned"; an ID always selects a user-assigned identityPosiblemente ocupada @g2vinay la tomó hace 1 día. AbiertoAzure.Identity Client customer-reported needs-team-attention question
Azure/azure-sdk-for-java#50995 · 2 comentarios · 1 reacción · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
[FEATURE REQ] Update Patch Releases to Include 3P dependenciesPosiblemente ocupada @jairmyree la tomó hace 1 día. Abiertoblocked
Azure/azure-sdk-for-java#50993 · 1 reacción · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
Azure.Core.V2 clientcore
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Azure/azure-sdk-for-java#50936 · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
Paging extenisonsAbiertoclientcore
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
Azure/azure-sdk-for-java#50935 · 1 reacción ·
Los mantenedores suelen responder en 1 día
Todos los issues de Azure/azure-sdk-for-java
Issues similares
-
bug web-server
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
jooby-project/jooby#4035 ·
Los mantenedores suelen responder en 1 día
-
L: javascript
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
dependabot/dependabot-core#16744 ·
Los mantenedores suelen responder en 1 día
-
intake mcp-intake needs-human-review priority:high type:bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Ikalus1988/MisakaNet#3987 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
quarkiverse/quarkus-google-cloud-services#1187 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
locationtech/jts#1250 ·