Skip to content

GH-5107 MySQL queue sender moves the outbox row atomically for every durable send - #5113

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-5107-mysql-queue-sender
Oct 21, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
gh-5107-mysql-queue-sender

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Follow-up to #5107 / #5109 for the MySQL queue sender. Independent of #5109; each database engine gets its own PR.

The gap

MySqlQueueSender.SendAsync(Envelope) only took the atomic MoveFromOutgoingTo* path when Envelope.WasPersistedInOutbox was true. Envelopes stored by the EF Core / Marten outbox and envelopes resent by outbox recovery are not marked, so they were inserted into the queue on their own and the outbox row was deleted later, in a separate transaction by DurableSendingAgent. A node that dies between the two leaves the outbox row behind; outbox recovery resends it, and once the receiver's handled inbox row has expired (KeepAfterMessageHandling) the copy is handled a second time.

Fix

A durable send that isn't marked WasPersistedInOutbox now goes through MoveOrWriteToQueueAsync, which writes the queue (or scheduled) row and deletes the outbox row, if there is one, in one transaction. A client transaction; a duplicate is a no-op ON DUPLICATE KEY UPDATE id = id, so the delete still commits. (Not changed here: the existing marked MoveFromOutgoing* path runs its insert and delete as two autocommitted statements, so it isn't atomic either.) Marked envelopes keep the existing path; non-durable endpoints are unchanged. The DurableSendingAgent delete that follows becomes a no-op.

Tests

New Transport/Bug_5107_outbox_row_survives_the_queue_insert.cs:

  • the queue insert removes the outbox row
  • a scheduled send removes the outbox row
  • an envelope with no outbox row still reaches the queue
  • sending the same envelope twice leaves one queue row and no outbox row

Before the fix 3 of 4 failed ("outbox count should be 0 but was 1"); the no-outbox-row test was the control. After: 4/4. Locally on net9.0 the provider's Transport tests pass (60/60), and wolverine.slnx builds clean with -f net9.0.

Refs #5107

🤖 Generated with Claude Code

https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U

…durable send

Durable sends without WasPersistedInOutbox now write the queue or scheduled row and delete the outbox row
in one transaction; a duplicate send is a no-op ON DUPLICATE KEY UPDATE so the delete still commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
(cherry picked from commit d3f3778)
@jeremydmiller
jeremydmiller merged commit e209289 into main Oct 21, 2026
47 checks passed
This was referenced Oct 23, 2026
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.

1 participant