Skip to content

fix(core-amqp): carry the cause of a CBS open failure to the caller - #7384

Merged
Johnathan W (j7nw4r) merged 1 commit into
Azure:mainfrom
sagar0207:fix/cbs-open-failure-root-cause
Aug 29, 2026
Merged

fix(core-amqp): carry the cause of a CBS open failure to the caller#7384
Johnathan W (j7nw4r) merged 1 commit into
Azure:mainfrom
sagar0207:fix/cbs-open-failure-root-cause

Conversation

@sagar0207

@sagar0207 Sagar Patel (sagar0207) commented Aug 28, 2026

Copy link
Copy Markdown
Member

Problem

A claims based security open reports every transport, TLS and link failure as CbsOpenResult::Error. A caller holding only that result cannot tell a refused socket from a rejected attach.

The reason existed at each layer and was written to the log, then dropped. ManagementClientImpl::Open reports a failure as a status, so the handler that caught the exception logged e.what() and returned an enum:

catch (std::runtime_error const& e)
{
  Log::Stream(Logger::Level::Warning) << "Exception thrown opening message sender and receiver." << e.what();
  CloseSenderAndReceiverAfterFailedOpen();
  return _internal::ManagementOpenStatus::Error;   // the cause is destroyed here
}

A caller that logs the exception and runs no log listener therefore read only:

Could not open Claims Based Security object. Result: Error, audience: amqps://..., caller: ConnectionImpl::AuthenticateAudience.

What this changes

The reason now travels with the status and reaches the CbsOpenFailedException message, so it is visible to a caller that never enables the SDK logger. It names which of the two links failed, because the sender and the receiver fail for different causes.

ClaimsBasedSecurityImpl::OnError receives the condition, the description and the info map the service sent, which is its richest statement about a refused claim, and only wrote them to the log. Those now reach the reason as well.

This change is diagnostics only. It alters no control flow and no state machine.

Result

Captured from the live failure test, verbatim:

Could not open Claims Based Security object. Result: Error,
  audience: amqps://localhost/testEntity,
  caller: ConnectionImpl::AuthenticateAudience,
  reason: the message sender open threw: Could not open message sender. errno=0, "No error".

The shape of the reason separates the layers. A reason that says the open threw never reached the service. A reason that carries an AMQP condition means the service answered and refused. That distinction is the one a caller currently cannot make at all.

Thread safety

OnError runs on the polling thread while ManagementClientImpl::Open holds its open lock across WaitForResult, so that capture takes a lock of its own; taking the management open lock there would block the polling thread for the whole open.

For the same reason the open complete queue carries the reason with the status (AsyncOperationQueue<ManagementOpenStatus, std::string>) rather than a member the completing handler would have to lock.

Compatibility

detail is a defaulted parameter on both text builders, so existing call sites are unchanged and an empty reason produces byte identical text. ManagementClientImpl is only a complete type inside the library, so no exported layout changes. No message holds the token; the $cbs client never calls AuthenticateAudience and both of its links set AuthenticationRequired = false.

Validation

  • azure-core-amqp-tests: 228 / 230 pass, which matches the baseline on this commit's parent exactly.
  • The two failures (TestCbs.CbsOpenNoListener, TestMessageSendReceive.SenderCloseWhileUnsettledSendIgnoresLateDisposition) reproduce on clean main at 9dacd081 with none of this change applied. They are pre-existing and are not addressed here.
  • New coverage: two cases in TestCbsOpenFailureText for the reason in the exception text and in the log line, and three assertions added to TestCbs.AuthenticationFailureNamesTheCbsOpenFailure confirming the reason reaches a real failure, names the failing link, and never carries the generic state error. The existing sentinel token assertions still confirm no credential reaches any message or log line.

Deliberately not included

Two further changes were prototyped and withdrawn from this PR because they could not be covered by a regression test, and both are behavioural rather than diagnostic:

  1. Preferring a detach condition seen while the sender is still opening. When the service rejects a link during the open, m_senderOpen is still false, so OnLinkDetached discards the error and the open reports amqp:internal-error with "Message Sender entered the Error State." Attempts to reproduce this against the mock failed: a bare DETACH is not routed to the client's detach handler because the link never attached, and sending ATTACH then DETACH from inside OnLinkAttached aborts with "Failed to send detach performative".

  2. Completing the open when a $cbs link is disconnected while the management client is opening. Today the disconnect handlers move the client to the error state, the link's own transition to that state is then ignored, and nothing completes the open, so Open waits for the caller's context to be cancelled and reports Cancelled rather than Error. Because only Error is retried, that also disables the retry. Reproducing it needs the mock to hold or delay a link attach, which it cannot currently do — the open completes as soon as both $cbs links attach, so there is no window to act in.

Both are worth doing. They need a delayed-attach capability in AmqpServerMock first, and they belong in a PR that can demonstrate them.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
8 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Propagates detailed CBS open-failure causes through the AMQP stack to logs and exceptions.

Changes:

  • Carries management/link failure details alongside open status.
  • Preserves service detach errors and prevents stranded opens.
  • Adds failure-message tests and changelog entries.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
CHANGELOG.md Documents improved CBS diagnostics.
src/amqp/connection.cpp Adds failure details to logs and exceptions.
src/amqp/private/cbs_open_failure.hpp Formats optional failure details.
src/impl/uamqp/amqp/claim_based_security.cpp Collects management and service errors.
src/impl/uamqp/amqp/private/claims_based_security_impl.hpp Adds synchronized error storage.
src/impl/uamqp/amqp/management.cpp Propagates link-specific open failures.
src/impl/uamqp/amqp/private/management_impl.hpp Stores and queues failure details.
src/impl/uamqp/amqp/message_sender.cpp Preserves detach errors during open.
src/impl/uamqp/amqp/private/message_sender_impl.hpp Adds open-specific detach storage.
src/impl/rust_amqp/amqp/claim_based_security.cpp Implements backend-compatible detail accessor.
src/impl/rust_amqp/amqp/private/claims_based_security_impl.hpp Declares detail accessor.
test/ut/claim_based_security_tests.cpp Extends CBS failure assertions.
test/ut/connection_tests.cpp Tests detailed failure formatting.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread sdk/core/azure-core-amqp/src/impl/uamqp/amqp/message_sender.cpp Outdated
Comment thread sdk/core/azure-core-amqp/src/impl/uamqp/amqp/management.cpp Outdated
Comment thread sdk/core/azure-core-amqp/src/impl/uamqp/amqp/management.cpp Outdated
@sagar0207
Sagar Patel (sagar0207) force-pushed the fix/cbs-open-failure-root-cause branch from d695bb2 to 6260228 Compare August 28, 2026 21:49
A claims based security open reports every transport, TLS and link failure as
`CbsOpenResult::Error`, so a caller holding only that result cannot tell a
refused socket from a rejected attach. The reason existed at each layer and was
written to the log, then dropped: `ManagementClientImpl::Open` reports a failure
as a status, and the exception that named the cause was destroyed in its
handler. A caller that logs the exception and runs no log listener therefore had
nothing to read.

The reason now travels with the status and reaches the `CbsOpenFailedException`
message. It names which of the two links failed, because the sender and the
receiver fail for different causes, and the shape of the reason separates the
layers: a reason that says the open threw never reached the service, while a
reason that carries an AMQP condition means the service answered and refused.

`ClaimsBasedSecurityImpl::OnError` receives the condition, the description and
the info map that the service sent, which is its richest statement about a
refused claim, and only wrote them to the log. They now reach the reason as well.
That capture takes a lock of its own, because the callback runs on the polling
thread while the management client holds its open lock.

The open complete queue carries the reason with the status, so the handlers that
complete it from the polling thread never take the lock that `Open` holds.

This change is diagnostics only. It alters no control flow and no state machine.

No message holds the token.

Copilot-Session: 7ae0b8be-ee18-4b04-8ddf-158d6ffbd1c9
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
8 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@j7nw4r
Johnathan W (j7nw4r) enabled auto-merge (squash) August 29, 2026 12:13
@j7nw4r
Johnathan W (j7nw4r) merged commit d696adc into Azure:main Aug 29, 2026
93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants