[BUG] Service Bus Java scheduleMessage does not retry a transient server-busy response despite maxRetries=3
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 20/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 停滞
- 技術スタック
- azure, java
調査の方向性
Start by reading ServiceBusSenderAsyncClient.scheduleMessageInternal and ManagementChannel.sendWithVerify, then follow RequestResponseChannel.sendWithAck and settleMessage to understand where the 503 response becomes a SERVICE_BUSY exception. Use the attached standalone Maven reproduction, especially SchedulingRetryReproducerTest.java and SchedulingRetryExpectedBehaviourIT.java, to confirm the missing retry and expected success after a second send. Done means the scheduling call follows the intended retry policy for this transient response, or the SDK documents the limitation and recommended application retry boundary.
索引モデルが issue の本文から書いたものです。
説明
Describe the bug
With com.azure:azure-messaging-servicebus:7.18.1, the public scheduling API returns a transient ServiceBusException with reason SERVICE_BUSY after a single scheduling send when the broker's management response carries:
status-code: 503
error-condition: com.microsoft:server-busy
status-description: Error code: 50009. Entity is being throttled.
This occurs with AmqpRetryOptions.maxRetries=3. Our fixture would return a successful scheduling response on the next send, but the SDK does not make that second attempt. A transient failure while opening the send link is retried successfully in a separate positive control.
We encountered this while investigating retry handling for scheduled timeout messages. Applications relying on automatic SDK retry for scheduling can instead receive the transient rejection immediately and need their own operation-level policy.
The attached reproduction uses published SDK binaries with simulated broker responses. It is an isolated unit reproduction, not a live Azure throttling test or a claim that every production throttling error follows this path.
Exception and observed result
The scheduling call throws ServiceBusException; the tests assert:
assertEquals(ServiceBusFailureReason.SERVICE_BUSY, error.getReason());
assertTrue(error.isTransient());
assertEquals(1, fixture.sends.get());
The exception description contains the synthetic 50009 message above. The companion archive includes the observed expected-behaviour test failure in evidence/expected-regression-results.txt.
To reproduce
Archive SHA-256: 89d383d05ee3a533f4c2bb5767996856090ca8e20f15eca4e6d1c50b8381bfd3.
Use the attached servicebus-scheduling-retry-reproducer.zip, which contains a standalone Maven project, test source and captured evidence. It requires Java 21 and Maven. No application repository, Azure credentials, live namespace or running broker is required. Maven needs access to dependencies on the first run.
-
Extract the archive and enter
servicebus-scheduling-retry-reproducer/. -
Run the diagnostic/characterisation suite:
mvn -B -ntp testExpected on the affected version: six tests pass. These tests assert the observed missing retry and the controls; their passing does not mean the scheduling retry succeeded.
-
Run the separate regression test that expects automatic retry:
mvn -B -ntp -Dtest=SchedulingRetryExpectedBehaviourIT testThis test fails on 7.18.1:
scheduleMessagethrows the first busy exception instead of consuming the fixture's second successful response and returning sequence number 42. TheITsuffix keeps this expected failure out of the default diagnostic run.
The diagnostic cases cover:
| Case | Observed behaviour |
|---|---|
| Public scheduling with transient send-link failure, then success | Two link-opening subscriptions; one successful scheduling send |
| Public scheduling with a busy management response, then success if retried | Transient SERVICE_BUSY after one scheduling send |
| Real request/response channel receives that busy response | Emits the response object containing status 503 |
| Real management channel interprets the response | Maps it into transient SERVICE_BUSY, without another send |
| Existing SDK retry helper wrapped around management scheduling | Still propagates the mapped Service Bus exception after one send |
| Explicit bounded retry around management scheduling | Reaches the second response and succeeds; the input message retains its business ID and scheduled time |
Code snippet
The public sender in the reproduced scheduling case is configured as follows:
ServiceBusSenderAsyncClient client = new ServiceBusClientBuilder()
// Deliberately fake; its connection is replaced by the fixture before use.
.connectionString("Endpoint=sb://audit.invalid/;SharedAccessKeyName=audit;SharedAccessKey=YXVkaXQ=")
.retryOptions(new AmqpRetryOptions()
.setMaxRetries(3)
.setTryTimeout(Duration.ofSeconds(2))
.setDelay(Duration.ofMillis(1))
.setMaxDelay(Duration.ofSeconds(5)))
.sender()
.queueName("audit-queue")
.buildAsyncClient();
// No application retry surrounds the public call in the failing regression.
client.scheduleMessage(message, originalDueAt).block(Duration.ofSeconds(15));
The complete fixture is in src/test/java/com/example/servicebus/SchedulingRetryReproducerTest.java. The expected-behaviour regression is alongside it in SchedulingRetryExpectedBehaviourIT.java.
The public sender, ManagementChannel and RequestResponseChannel are real SDK implementations. Connection acquisition, authentication, channel cache, serializer, Proton links, dispatcher and handler events are test fixtures. The serializer supplies the synthetic successful sequence number 42. Response status interpretation and the retry operators being investigated execute in real SDK code. Reflection is used to instantiate internal constructors and inject the fake connection; this is white-box reproduction code, not suggested application usage.
Each send passes through the real request/response channel's encoding and correlation handling. Counts come from the fake Proton send callback, not actual network packets. Both channel and public client have three retries, a two-second try timeout, a one-millisecond configured delay and a five-second maximum delay. The overall wait permits the SDK's additional backoff. These timings, and the positive control's one-millisecond application retry delay, are test settings rather than production guidance.
Expected behaviour
For this explicit transient busy rejection, the scheduling operation should be retried according to the configured SDK policy. With a busy response followed by success, we expect the call to succeed and return the successful sequence number after two scheduling sends.
Please confirm whether automatic retry of this scheduling management response is intended SDK behaviour. If this response is intentionally excluded from automatic retry, please document that limitation and the recommended application retry boundary. This report does not propose treating a lost response or uncertain acceptance as a definite refusal.
Setup
- Library:
com.azure:azure-messaging-servicebus:7.18.1 - Resolved Azure core:
com.azure:azure-core:1.60.0 - Resolved Azure AMQP:
com.azure:azure-core-amqp:2.12.2 - Test dependencies: JUnit Jupiter 5.11.4, Mockito 5.17.0
- Java: OpenJDK 21.0.10, Homebrew distribution
- Maven: 3.9.12
- OS: macOS 14.6.1, Apple Silicon / aarch64
- IDE: none required; reproduced from Maven CLI
- App server/framework: none in the standalone reproduction
- Broker: simulated AMQP management responses; no live Azure broker in this test
The portable project's POM configures Mockito's Java agent through Maven's local-repository setting, without machine-specific paths. Runtime Surefire classpaths were checked to verify the resolved SDK/core versions.
Earlier probes also observed the failure through the public synchronous and asynchronous APIs:
| Service Bus SDK | Azure core | Azure core-amqp | Busy scheduling response |
|---|---|---|---|
| 7.17.9 | 1.57.1 | 2.11.3 | One scheduling request, then transient SERVICE_BUSY |
| 7.17.11 | 1.57.1 | 2.11.3 | Same |
| 7.18.1 | 1.60.0 | 2.12.2 | Same |
The first two rows held our resolved core dependencies constant. Those earlier probes mocked RequestResponseChannel; the attached 7.18.1 project strengthens the reproduction by executing the real request/response channel as well. The archive focuses on the standalone 7.18.1 case rather than claiming to rerun the full version matrix.
Additional context: suspected retry boundary
Inspection of the exact published 7.18.1 and core-amqp 2.12.2 sources suggests:
ServiceBusSenderAsyncClient.scheduleMessageInternalretriesgetSendLinkWithRetry(...), then invokesmanagementNode.schedule(...)downstream of that retry.RequestResponseChannel.sendWithAckretries endpoint readiness, then performs the request/response work outside that retry.RequestResponseChannel.settleMessageemits a received management response withsink.success(message), including a response carrying status 503.ManagementChannel.sendWithVerifythen interprets the unsuccessful status, creates an AMQP exception and maps it toServiceBusException. There is no enclosing scheduling-operation retry at this point.
Relevant source: sender scheduling, management response verification, request/response send, and response settlement. These immutable links identify commit 30a1e8c2c765d3f016965cc26ca8cc660f32c6b6; all three Java files were verified to match the published source files exactly. The version-specific analysis used the published 7.18.1 source artifact and core-amqp 2.12.2 source artifact. The GitHub commit is a verified source reference, not a claim about the release commit.
There is a further type-boundary detail: RetryUtil.createRetry accepts transient AmqpException and timeouts, whereas management scheduling has already mapped its error to ServiceBusException. The included helper-control test demonstrates that simply placing the existing SDK retry helper outside the mapped management operation does not retry this error. This is supporting diagnosis, not a proposed production patch.
The 7.17.11 fix, PR #44920, adds retry around link creation. The attached link-failure positive control verifies that this retry works; the scheduling-response case occurs later. The 7.18.1 release notes list dependency updates rather than a scheduling-response retry change.
Related issues
- #24620 — Apply AmqpRetryPolicy to RequestResponseChannel.sendWithAck: concerns missing acknowledgement timeout/retry in the same underlying API. The maintainer discussion mentions performance considerations. This report concerns an explicit busy response after successful response delivery, rather than an absent acknowledgement.
- #34646 — Failure to send when request response channel is disposed, with no retry: similar missing-retry symptom, but its stack fails during authentication/channel disposal rather than a scheduling busy response.
- #35511 — Retry com.microsoft:operation-cancelled errors: related management-response classification, involving
peekMessageand a different error condition. - PR #44920 — Add retry for create-send-link in single-message send: related fix with a narrower retry boundary, as described above.
We did not locate an existing report precisely covering the attached scheduling busy-response reproduction. Please link or deduplicate this report if one exists.
Information checklist
- Bug description added
- Standalone reproduction and run commands added
- Setup and resolved dependency versions added
- Expected and actual behaviour distinguished
- Simulated broker boundary and limitations stated
- Related issues and earlier retry fix linked
- 主要言語
- Java
- スター
- 2.6k
- フォーク
- 2.2k
- 平均マージ
- 2日 3時間
- マージ済み PR(30日)
- 230
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Azure/azure-sdk-for-java のほかの issue
-
[Spring Cloud Azure] Improve Javadoc and startup guidance for allowed-group-names vs allowed-group-ids in AadAuthenticationProperties対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンazure-spring azure-spring-aad Client
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Azure/azure-sdk-for-java#51046 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
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] Cosmos: `byPage(token, size).next()` sends an extra speculative query for the next page (~2x queries/RU per page call)対応中かも @FabianMeiswinkel が今日担当しました。 オープンClient Cosmos customer-reported needs-team-attention question Service Attention
Azure/azure-sdk-for-java#51034 · コメント 3 件 · リアクション 1 件 · 担当者 2 名 ·
メンテナーはふだん 1 日以内に返信
-
[FEATURE REQ] Support minor version bump in patch release process対応中かも @jairmyree が 2 日前に担当しました。 オープン
Azure/azure-sdk-for-java#51013 · リアクション 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
[Spring Cloud Azure] Validate tfp/acr claim against configured user-flows in AadB2cResourceServerAutoConfiguration対応中かも @domainname が 1 日前に担当しました。 オープンazure-spring azure-spring-aad-b2c Client
難易度 3/5 1〜2日 初心者へのやさしさ 76/100
Azure/azure-sdk-for-java#51008 · リアクション 1 件 · 担当者 3 名 ·
メンテナーはふだん 1 日以内に返信
Azure/azure-sdk-for-java の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
quarkusio/quarkus-agent-mcp#302 ·
メンテナーはふだん 2 日以内に返信
-
build
難易度 1/5 1時間未満 初心者へのやさしさ 85/100
MetricsHub/metricshub-community#1472 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
IdentityArrayObjectSet iterator remove may throw ArrayIndexOutOfBoundsException対応中かも @jiteshkhatri11 が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
javapathfinder/jpf-core#660 · コメント 1 件 ·