Skip to content

[Deduplicated] on Marten: a concurrent [WriteAggregate] repeat gets a 500, not the 409, when its commit fails on the stream version first #5166

Closed

Description

@uniquelau

Wolverine 6.51.3, Wolverine.Http, Marten integration, PostgreSQL. Found while aligning #5161 with GH-4505.

What happens

GH-4505 queues the [Deduplicated] claim on the Marten session, and RefuseDuplicateClaimAtCommitFrame turns the one duplicate an optimistic check can't catch, two concurrent requests that both read "not claimed", into the endpoint's 409. Its exception filter, MartenDeduplicationFailures.IsDuplicateDeduplicationClaim, matches only a unique violation on the deduplication table.

On a [WriteAggregate] endpoint, both concurrent requests load the stream at the same version and both append. Marten fails the second commit on the stream version before it reaches the claim's INSERT, so the filter never matches. The loser's EventStreamUnexpectedMaxEventIdException escapes as a 500. The work is still done only once, so this is about the answer, not duplicate work.

Reproduction

Two concurrent requests with the same Idempotency-Key, held at a barrier inside the endpoint so both have loaded the stream before either appends:

[Deduplicated]
[WolverinePost("/probe-dedup-race/aggregate")]
public static async Task<ProbeRaceIncremented> PostAggregate(ProbeRaceIncrement command,
    [WriteAggregate] IEventStream<ProbeRaceCounter> stream)
{
    await bothArrive(command.Note);
    var e = new ProbeRaceIncremented(command.Note);
    stream.AppendOne(e);
    return e;
}

Host: MessageDeduplicationMode.CompareByHash, AutoApplyTransactions(), Marten with IntegrateWithWolverine(). The generated code has the GH-4505 frames (QueueClaim, the IsDuplicateDeduplicationClaim filter).

Endpoint First request Concurrent repeat Committed
[WriteAggregate] append 200 throws EventStreamUnexpectedMaxEventIdException (500) 1 event
session.Store(...) control 200 409 1 document

An application using MapMartenConcurrencyFailuresToConflict() will see a 409 anyway, from that policy instead of from deduplication.

Possible fix

What #5161 does for [DeduplicatedWithResponse(ClaimInTransaction = true)]: wrap only the commit, and on any failure check whether the key is now claimed. If it is, answer with the refusal; if not, rethrow unaltered. A marker after the commit keeps later failures out. A claim committed meanwhile can only come from a concurrent request with the same key, so answering from it is right whatever the commit failed on.

Related: GH-4505, #5160, #5161.

Activity

  1. added a commit that references this issue on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions