[BUG] AccessTokenCache/SimpleTokenCache: concurrent callers wait forever when the caller refreshing the token is cancelled
メンテナーはふだん 1 日以内に返信
評価
この issue はまだ評価されていません。
説明
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
- 主要言語
- Java
- スター
- 2.6k
- フォーク
- 2.2k
- 平均マージ
- 1日 22時間
- マージ済み PR(30日)
- 213
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Azure/azure-sdk-for-java のほかの issue
-
mgmt, in track 1 migration guide, call out that file-based authentication method is not recommended再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンAzure.Identity
難易度 1/5 1時間未満 初心者へのやさしさ 74/100
Azure/azure-sdk-for-java#48894 ·
メンテナーはふだん 1 日以内に返信
-
[BUG] - Handler.close livelocks on FAIL_NON_SERIALIZED while holding the ReactorConnection monitor対応中かも @conniey が 1 日前に担当しました。 オープンAzure.Core.AMQP Client customer-reported needs-team-attention question
Azure/azure-sdk-for-java#50879 · コメント 2 件 · リアクション 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
[Design] Support External Evaluation Policy (Invoke) in management SDKs対応中かも @weidongxu-microsoft が 6 日前に担当しました。 オープン
Azure/azure-sdk-for-java#50818 · リアクション 2 件 · 担当者 2 名 ·
メンテナーはふだん 1 日以内に返信
-
[BUG] azure-core-http-vertx: Streaming request bodies hang on Vert.x 5.x (NoSuchMethodError on HttpClientRequest.reset)対応中かも @alzimmermsft が 6 日前に担当しました。 オープンAzure.Core Client customer-reported needs-team-attention question
Azure/azure-sdk-for-java#50793 · コメント 6 件 · リアクション 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
[FEATURE REQ] ChangeFeedProcessor event handler fully reactive implementation対応中かも @FabianMeiswinkel が 7 日前に担当しました。 オープンClient Cosmos customer-reported feature-request needs-team-attention Service Attention
難易度 5/5 1週間以上 初心者へのやさしさ 12/100
Azure/azure-sdk-for-java#50758 · コメント 3 件 · リアクション 1 件 · 担当者 2 名 ·
メンテナーはふだん 1 日以内に返信
Azure/azure-sdk-for-java の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
apache/rocketmq-dashboard#7302 ·
メンテナーはふだん 3 日以内に返信
-
area: android good first issue help wanted testing
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
Gautam-aman/ResQNet#56 ·
メンテナーはふだん 1 日以内に返信
-
documentation hermes-queue
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
chrisalunlloyd2-sudo/mindpalace#294 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
QuicksilverMC/Argentum#31 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
avniproject/avni-server#1098 · コメント 2 件 ·