Skip to content

fix(eventhubs): make a read after a partition close throw - #7337

Draft
Johnathan W (j7nw4r) wants to merge 2 commits into
feat/link-reattachfrom
fix/7334-close-stops-receiver-rebuild
Draft

fix(eventhubs): make a read after a partition close throw#7337
Johnathan W (j7nw4r) wants to merge 2 commits into
feat/link-reattachfrom
fix/7334-close-stops-receiver-rebuild

Conversation

@j7nw4r

Copy link
Copy Markdown
Member

Summary

PartitionClient::Close records no state. A later ReceiveEvents on the same object does not report that the client is closed. The defect exists only on feat/link-reattach (#7328) and has not reached main, so this pull request stacks on that branch.

Motivation

Observed behavior before the fix, on the uAMQP transport: a read after Close blocked for the full read deadline (60 seconds measured) and then threw a raw Azure::Core::OperationCancelledException. That is the wrong error type and a long stall. Close clears the message queue, frees the link, and clears m_receiverOpen, so no AMQP error reaches the queue and the recover path never runs. TryWaitForIncomingMessage returns empty with no error, and ReceiveEvents falls through to WaitForIncomingMessage, which blocks until the deadline. The rebuild path that issue #7334 describes remains plausible on the Rust transport and was not proven.

Changes

  • Adds std::atomic<bool> m_closed{false} to PartitionClient; the header now includes <atomic>.
  • Close sets the flag before it closes the receiver.
  • ReceiveEvents tests the flag as its first action and throws.
  • The recover lambda tests the flag after the backoff and before RebuildReceiver. When the call already holds events, it returns them; when it holds none, it throws.
  • Adds a file-local CreateClientClosedException helper that sets IsTransient to false and leaves ErrorCondition empty.
  • Replaces the defaulted move constructor and move assignment with hand-written ones. std::atomic has a deleted copy constructor and no move constructor, so the defaulted move operations would become deleted. The build is C++14 with no guaranteed copy elision, and ConsumerClient::CreatePartitionClient returns PartitionClient by value. Copy stays deleted.
  • Adds a CHANGELOG entry under 1.0.0-beta.14 (Unreleased), Bugs Fixed.

Known limitation: the closed test inside the recover loop has no automated test. It fires only when a link fault and a Close overlap inside the rebuild backoff, and this target has no mock AMQP server.

Test plan

  • ReattachConnectionStringTest.ReceiveAfterCloseThrowsWithoutARebuild_LIVEONLY_ closes, closes again, reads, and asserts the throw with IsTransient false and an empty ErrorCondition. It then repeats the read on a moved-from-closed client. A mirror TEST_P on ConsumerClientTest covers the AAD live pass.
  • Four static_asserts pin move-constructible, move-assignable, and copy-deleted. They compile in every build, so ordinary CI checks them.
  • The uAMQP tree and the Rust tree both build and link azure-messaging-eventhubs-test.
  • Live, uAMQP: ReattachConnectionStringTest.* reports 2 passed, 3 skipped, 0 failed. The new test passes in 6244 ms, which is connect plus send, not a blocked read.
  • Live, Rust: the new test passes in 5619 ms.
  • Non-live suite on both trees: 67 passed, 1 failed. The failure is CheckpointStoreTest.TestCheckpoints with a missing EVENTHUB_CONSUMER_GROUP, pre-existing and unrelated.
  • Before the fix, the same test failed with a 66 second run and C++ exception with description "Receive Operation was cancelled." thrown in the test body.

Closes #7334

A read after PartitionClient::Close must throw a non-transient
EventHubsException, and the client must not attach a new receiver.
Close sets no state today, so the next ReceiveEvents goes on to the
link (#7334).

Add ReceiveAfterCloseThrowsWithoutARebuild_LIVEONLY_ to the connection
string suite and to the parameterized consumer suite. Each test closes
the partition client twice, which pins the idempotent close, then reads
and requires the throw. Each test then moves the closed client and
reads again, so the closed state must survive a move.

Add four static_asserts on PartitionClient. They keep the move
available and the copy deleted, so a member that is not movable by
default fails the compile instead of the link.
PartitionClient::Close closed the AMQP receiver and set no state on the
client. A later ReceiveEvents therefore went on to the closed link. On
uAMQP that read gave back an empty pair with no AMQP error, so the call
fell through to WaitForIncomingMessage and blocked until the read
deadline, then threw a raw OperationCancelledException.

Close now marks the client closed before it closes the receiver.
ReceiveEvents tests that flag first and throws a non-transient
EventHubsException with no AMQP condition. The recover loop tests the
same flag before each rebuild attempt, so a Close that runs during a
call stops the loop instead of attaching a new receiver on the still
open session. A call that already holds events returns them, and it
keeps no pending error, because the next call sees the flag and throws.

The flag is a std::atomic<bool>, which has no implicit move, so the
move constructor and the move assignment are now hand written.

Fixes #7334
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
10 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

Fixes PartitionClient reads after closure by tracking closed state and returning a non-transient exception.

Changes:

  • Adds atomic closed-state tracking and custom move operations.
  • Prevents receiver recovery after closure.
  • Adds live tests and release notes.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
partition_client.cpp Implements closed-state handling and move operations.
partition_client.hpp Declares closed state and documents behavior.
consumer_client_test.cpp Tests closure and move semantics.
reattach_connection_string_test.cpp Adds connection-string coverage.
CHANGELOG.md Documents the fix.

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

Comment thread sdk/eventhubs/azure-messaging-eventhubs/src/partition_client.cpp
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.

2 participants