fix(eventhubs): stop delivery and release ownership on shutdown - #5108
Draft
Johnathan W (j7nw4r) wants to merge 2 commits into
Draft
fix(eventhubs): stop delivery and release ownership on shutdown#5108Johnathan W (j7nw4r) wants to merge 2 commits into
Johnathan W (j7nw4r) wants to merge 2 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 3 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
`EventProcessor::shutdown` only flips `is_running`. It closes no receiver and it releases no ownership record, so a partition client that the application holds keeps delivering events after shutdown returns. Add five offline tests that fail against the unchanged source, and one characterization test that guards the `Send` bound on the shutdown future. Add the same stop assertions to the live `receive_events_from_processor` test. Move `receiver_with_failing_attach` out of the private `mod tests` in `event_receiver.rs` and behind `#[cfg(test)] pub(crate)`, so the processor tests can give a partition client a receiver that answers offline. The harness still builds its own consumers map, because `EventProcessor` has no `consumers` field yet. When that field lands, return `processor.consumers.clone()` from the harness. The test bodies do not change. Refs #5096
`EventProcessor::shutdown` only set the `is_running` flag. It closed no receiver and released no ownership record, so a partition client that the application already held kept delivering events after `run` returned, and another instance had to wait for the ownership expiration to take the partitions. The processor now keeps its consumers map on `self`, so `run` and `shutdown` share one map. A new private `stop` sets the flag in a scoped block, closes the receiver of every partition client in the map, and releases the ownership records of this instance. It keeps the ETag that the store returned, because a claim with a stale ETag is rejected. A failure of the release logs at the warning level and does not fail the call. `close` runs the same stop path before it drains the queued partition clients and closes the consumer client. `stop` drops the `is_running` guard before every await, because a `std::sync::MutexGuard` is not `Send` and the shutdown future must stay `Send`. `close_all_receivers` keeps the map entries, because a client that the application still holds must keep its place. Fixes #5096
Johnathan W (j7nw4r)
force-pushed
the
j7nw4r/fix-eventhubs-shutdown-closes-receivers
branch
from
August 25, 2026 18:13
3f89beb to
5a4b84a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
EventProcessor::shutdownnow stops the event delivery and releases the ownership records that the instance holds. It set an internal flag and did nothing else before, so a partition client that the application already held kept delivering events afterrunreturned.Motivation
shutdownsetis_running = falseand stopped there.runbuilt its consumers map as a local value and stored it nowhere, soshutdowncould reach no receiver at all. Every receiver stayed attached, and every ownership record stayed live, so a second instance could not claim those partitions until the records expired.closeclosed receivers, but it consumes the processor out of itsArc, which a caller that holds a partition client cannot do.shutdownnow guarantees two things. It stops the delivery on every partition client that the processor issued, including one the application still holds, and thestream_eventsstream of such a client resolves withConsumerDisconnected. It then releases the ownership records whose owner is this instance, so another instance can claim those partitions without a wait for the expiration. A failed release logs at the warn level and does not fail the call.closediffers fromshutdownin what it adds, not in what it stops.closeruns the same stop path, and it also consumes the processor, drains the queued partition clients, and closes the consumer client.closeis therefore a superset ofshutdown.Changes
EventProcessorkeeps its consumers map onself, andrunuses that map in place of a local one.ProcessorConsumersMap::close_all_receivers, which closes each receiver and removes no map entry, so a partition client that the application retains keeps its place.stopthatshutdownandcloseshare. It drops theis_runningguard before it awaits, because astd::sync::MutexGuardis notSendand holding it across an await deadlocks.claim_ownershiprejects a stale ETag.runreads the shutdown flag only after theupdate_intervalsleep, soruncan take up to one fullupdate_intervalto return, while the delivery stops as soon asshutdownreturns.Test plan
mod testsofprocessor.rswere written first and proved red against the unchanged source:shutdown_closes_receivers_of_issued_partition_clients,shutdown_releases_only_this_instances_ownerships,shutdown_continues_when_the_ownership_release_fails,shutdown_twice_succeeds_and_keeps_ownership_released, andclose_runs_the_shutdown_stop_path.EventReceiverbefore it asserts, because a partition client with an empty receiver returns a canned error stream that pins nothing. An unclosed offline receiver yieldsAmqpError, and only the closed path yieldsConsumerDisconnected(None).shutdown_future_is_sendguards the deadlock. It becomes a compile error, not a failed assertion, if a later change holds theis_runningguard across an await.close_continues_past_a_retained_partition_clientstill passes, which holds the contract that a retained partition client stays in the consumers map.CARGO_BUILD_JOBS=1 cargo test --package azure_messaging_eventhubs --lib -- --test-threads=1reports 150 passed, 0 failed, 14 ignored.CARGO_BUILD_JOBS=1 RUSTFLAGS=-Dwarnings cargo test --no-run --package azure_messaging_eventhubsexits 0.cargo fmt --checkandcargo clippy --all-targetsare clean.receive_events_from_processor, which asserts that a retained partition client stops within 30 seconds ofshutdown. No live test ran in this session, so that assertion waits for a live pass.Closes #5096