Skip to content

fix(rdbms-inbox): align RavenDb and CosmosDb batch inbox with #2648 duplicate-envelope contract#2651

Merged
jeremydmiller merged 1 commit intoJasperFx:mainfrom
BlackChepo:fix/2648-followup-raven-cosmos-batch-dup
May 1, 2026
Merged

fix(rdbms-inbox): align RavenDb and CosmosDb batch inbox with #2648 duplicate-envelope contract#2651
jeremydmiller merged 1 commit intoJasperFx:mainfrom
BlackChepo:fix/2648-followup-raven-cosmos-batch-dup

Conversation

@BlackChepo
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #2648, which made the Wolverine inbox robust against
broker-redelivered duplicate messages by having DurableReceiver's
batch path catch a typed DuplicateIncomingEnvelopeException and
fall 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- EnvelopeException for 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 threw
DuplicateIncomingEnvelopeException for NonUniqueObjectException /
ConcurrencyException, but always populated Duplicates with
envelopes[0] regardless of which envelope actually collided.
With the new compliance assertion (Duplicates must contain exactly
the 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 findDuplicatesAsync helper that probes ExistsAsync
per 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] override
keeps passing.

CosmosDb

StoreIncomingAsync(IReadOnlyList<Envelope>) silently swallowed
HTTP 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 of
the 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.

Note: CosmosDb's message_store_compliance subclass is not
currently picked up by the build script's test-class discovery
(it inherits from MessageStoreCompliance without declaring
its own [Fact] methods, and DiscoverTestClasses only sees
classes with attribute hits in the file). The shared compliance
test added in #2648 therefore does not run on CosmosDb in CI
today — closing that discovery gap is out of scope here, but
the fix is in place for whenever it is.

Test plan

…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.)
@jeremydmiller jeremydmiller merged commit 284ca9a into JasperFx:main May 1, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants