fix(rdbms-inbox): align RavenDb and CosmosDb batch inbox with #2648 duplicate-envelope contract#2651
Merged
jeremydmiller merged 1 commit intoJasperFx:mainfrom May 1, 2026
Conversation
…uplicates Two providers had their own StoreIncomingAsync(IReadOnlyList) overrides that did not honor the new DurableReceiver fall-back contract. Both silently broke message handling once the receiver started relying on a typed DuplicateIncomingEnvelopeException to switch to the per-envelope path: - RavenDb threw DuplicateIncomingEnvelopeException(envelopes[0]) for both NonUniqueObjectException and ConcurrencyException. That made the first envelope in the batch look like the duplicate whenever the actual colliding envelope was not first — DurableReceiver then acked an innocent fresh envelope at the listener and the real duplicate slipped through. Replaced with a findDuplicatesAsync helper that probes ExistsAsync per envelope and reports only the ids that already exist. Falls back to the full batch when no pre-existing match is found (e.g. a same-identity intra-batch collision with no prior insert) so the per-envelope retry path still has something to sort, and the existing Raven-only "throws on [envelope, envelope]" test keeps passing. - CosmosDb silently swallowed HTTP 409 with the comment "DurableReceiver will retry one at a time" — but post-receiver change, the retry only fires when DuplicateIncomingEnvelopeException is thrown. Without it the batch returned successfully and every envelope, including the duplicate, was routed to the handler. Now collects the conflicting envelopes and throws DuplicateIncomingEnvelopeException(duplicates) at the end of the loop so the rest of the batch still commits. (CosmosDb's compliance subclass is not currently picked up by the build script's test-class discovery, so the new compliance test does not exercise this on CI — the fix is the same shape as the Oracle one and the shared compliance assertion documents the contract.)
This was referenced May 2, 2026
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
Follow-up to #2648, which made the Wolverine inbox robust against
broker-redelivered duplicate messages by having
DurableReceiver'sbatch path catch a typed
DuplicateIncomingEnvelopeExceptionandfall back to the per-envelope path (where each envelope is
correctly classified as fresh or duplicate).
That change moved the contract from "any exception means the inbox
is unavailable, pause the listener" to "throw
DuplicateIncoming- EnvelopeExceptionfor duplicates, anything else is a real failure."The RDBMS providers (Postgres, SqlServer, MySql, Sqlite, Oracle)
were brought in line as part of #2648, but two non-RDBMS providers
were missed because they have their own
StoreIncomingAsync(IReadOnlyList)implementations:
RavenDb
StoreIncomingAsync(IReadOnlyList<Envelope>)already threwDuplicateIncomingEnvelopeExceptionforNonUniqueObjectException/ConcurrencyException, but always populatedDuplicateswithenvelopes[0]regardless of which envelope actually collided.With the new compliance assertion (
Duplicatesmust contain exactlythe envelopes already in the inbox), this caused the per-envelope
fall-back to ack an innocent fresh envelope at the listener and let
the real duplicate slip into the handler.
Now uses a
findDuplicatesAsynchelper that probesExistsAsyncper envelope and reports only the ids that actually exist. Falls
back to the full batch when no pre-existing match is found (e.g.
an intra-batch identity collision with no prior insert) so the
per-envelope retry path still has something to sort, and the
existing Raven-only
throws on [envelope, envelope]overridekeeps passing.
CosmosDb
StoreIncomingAsync(IReadOnlyList<Envelope>)silently swallowedHTTP 409 with the comment "DurableReceiver will retry one at a
time" — true before #2648, no longer true after. Without a
thrown exception the batch returns successfully and every envelope
in the batch (including the duplicate) is routed to the handler,
re-introducing the double-execution that #2648 fixed.
Now collects the conflicting envelopes and throws
DuplicateIncomingEnvelopeException(duplicates)at the end ofthe loop, so the rest of the batch still commits while the
duplicate goes through the per-envelope path. Same shape as the
Oracle fix in #2648.
Test plan
bulk_store_intra_batch_duplicate_reports_only_actual_duplicatesassertion from fix(rdbms-inbox): discard duplicate Kafka messages instead of freezing the partition (#2639) #2648) runs green
bulk_store_with_intra_batch_duplicate_throws_DuplicateIncomingEnvelopeExceptionoverride stays green
against the Oracle fix in fix(rdbms-inbox): discard duplicate Kafka messages instead of freezing the partition (#2639) #2648 (CI does not currently exercise
the compliance test on CosmosDb)