fix(eventhubs): reject a checkpoint from an event with no position - #5107
Draft
Johnathan W (j7nw4r) wants to merge 2 commits into
Draft
fix(eventhubs): reject a checkpoint from an event with no position#5107Johnathan 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. |
The partition client returns Ok when an event carries no message annotations, and it writes a checkpoint with no offset and no sequence number when the annotations hold neither key. Both cases lose the caller's progress without a signal. Three tests fail against the unchanged source. A fourth does not compile, because it matches on an ErrorKind variant that the fix adds. Four more tests pass green. They pin the write path, the identity fields, and the store failure context.
PartitionClient::update_checkpoint returned Ok(()) and wrote nothing when the event had no message annotations. It also wrote a checkpoint with no offset and no sequence number when the annotations held neither value. Such a checkpoint names no position in the partition. It suppressed the per-partition start position the caller configured, because EventProcessor prefers any stored checkpoint over that position. It also erased a good checkpoint in BlobCheckpointStore, because the store builds the blob metadata from the checkpoint fields, and Azure Blob Storage replaces all metadata on a set-metadata call. The method now reads the offset and the sequence number through the ReceivedEventData accessors and returns the new error variant ErrorKind::MissingCheckpointMetadata when both are absent. An event that carries only one of the two still writes a checkpoint. This matches the InvalidOperationException that .NET raises for the same input. Refs Azure#5097
Johnathan W (j7nw4r)
force-pushed
the
j7nw4r/fix-eventhubs-checkpoint-no-annotations
branch
from
August 25, 2026 18:13
bc38ce7 to
e1f9b12
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
PartitionClient::update_checkpointnow returns an error when the event carries no offset and no sequence number. Such a call returnedOk(())and wrote nothing before, so a caller that checked every result still ended with no stored position.Motivation
The offset and the sequence number come from the message annotations, and two paths reached the same silent success. On the first path the annotations map was absent, and the method returned
Ok(())before it reached the store. On the second path the map was present but neitherx-opt-offsetnorx-opt-sequence-numberresolved, because the key was missing or its AMQP value had the wrong type, and the method wrote a checkpoint with both fields empty.The second path is the more damaging one, so the guard keys on the resolved values instead of on
message_annotations.is_none(). InEventProcessor::get_start_positiontheelse ifthat readsstart_positions.per_partitionhangs off the outercheckpoints.contains_key(partition_id)test. A checkpoint with both fields empty therefore enters the outer branch, matches neither value arm, and never reaches the per-partition lookup, so it silently overrides a per-partition start position the caller configured.BlobCheckpointStore::update_checkpointbuilds the blob metadata from the checkpoint fields, so the same record callsset_metadatawith an empty map, and Azure Blob Storage replaces all metadata on such a call. That erases a good checkpoint.Commit
adff670762(#3148) replaced two error returns in this method withcontinueand added the earlyOk(())return..NET reports the same input as an error.
EventProcessorClientraisesInvalidOperationExceptionwith the message "A checkpoint cannot be created or updated using an empty event." Go and Java do not error on this path, so this change claims parity with .NET only.Changes
ErrorKind::MissingCheckpointMetadata { partition_id }variant to the Event Hubs crate's ownErrorKind, so a caller can match on the kind instead of parsing the message text and can tell this failure apart from a store failure.azure_core::error::ErrorKindunchanged. That enum is not#[non_exhaustive], so a new variant there would be a major semver break, and it is not on this method's return path. The Event HubsErrorKindis already#[non_exhaustive], so this addition is not a breaking type change.update_checkpointto resolve the two values throughReceivedEventData::offsetandReceivedEventData::sequence_number, and to return the new error when both are absent. One check covers both causes.CHANGELOG.mdunder Features Added, Breaking Changes, and Bugs Fixed.The behavior change is deliberate and observable. A call that asks to record a checkpoint for an event with no position now fails where it used to report success, which is why the changelog carries a Breaking Changes entry.
Test plan
Eight unit tests were added to
partition_client.rs, and all of them run offline with no broker and no credential.ErrorKind::MissingCheckpointMetadataand read the partition id from it.Failed to update checkpoint for partitioncontext.The four error tests were proved red before the fix. The three behavioral tests failed against the unchanged source, and the two that cover the second cause printed the offending record,
Checkpoint { ..., offset: None, sequence_number: None }. The fourth failed to compile, because it names the new variant.Commands run in the worktree, each exiting 0.
CARGO_BUILD_JOBS=1 RUSTFLAGS=-Dwarnings cargo test --no-run --package azure_messaging_eventhubsCARGO_BUILD_JOBS=1 cargo test -p azure_messaging_eventhubs --all-features --lib -- --test-threads=1, giving 152 passed, 0 failed, 14 ignored, against a pre-change baseline of 144 passedcargo fmt -p azure_messaging_eventhubs -- --checkCARGO_BUILD_JOBS=1 cargo clippy -p azure_messaging_eventhubs --all-features --all-targets -- -DwarningsCARGO_BUILD_JOBS=1 RUSTDOCFLAGS=-Dwarnings cargo doc -p azure_messaging_eventhubs --no-deps --all-featuresNo live test was run for this change. The only in-repo caller,
tests/eventhubs_processor.rs, records checkpoints from real broker events, which always carry both annotations, so the guard does not change that path.The blob metadata erasure itself is not covered by a new test, because proving it end to end needs a live storage account. The fix stops such a record from reaching any store, and the second error test pins that cause.
Closes #5097