Skip to content

Run transaction participants when SaveChangesAsync has only participants pending#161

Merged
jeremydmiller merged 1 commit into
mainfrom
fix-savechangesasync-participants-only
May 28, 2026
Merged

Run transaction participants when SaveChangesAsync has only participants pending#161
jeremydmiller merged 1 commit into
mainfrom
fix-savechangesasync-participants-only

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

The bug

DocumentSessionBase.SaveChangesAsync early-returns when _workTracker.HasOutstandingWork() is false. _workTracker tracks document operations + event streams — it does not include ITransactionParticipants registered via AddTransactionParticipant. A session that has only registered participants (no doc ops, no streams) therefore had SaveChangesAsync skip the entire pipeline; the SQL transaction was never begun, and every queued participant's BeforeCommitAsync silently never ran.

How it surfaced

Wolverine's GH-2941. Wolverine.Polecat's outbox path for scheduled cascades (a DeliveryMessage<T>.DelayedFor(...) emitted by a [ReadAggregate] or [DocumentExists]-decorated handler with no document writes) hits Session.StoreIncoming(...), which registers a StoreIncomingEnvelopeParticipant whose BeforeCommitAsync inserts the scheduled envelope row into wolverine_incoming_envelopes. With no document ops on the session, that participant never ran and the scheduled message was silently lost.

Fix

Extend the early-return guard to also account for queued transaction participants. Listeners and other commit-time side effects continue to run via the normal pipeline once the transaction is begun.

- if (!_workTracker.HasOutstandingWork()) return;
+ if (!_workTracker.HasOutstandingWork() && _transactionParticipants.Count == 0) return;

Test

New regression test Bug_save_changes_runs_participants_only pins the contract that ITransactionParticipant.BeforeCommitAsync runs (and runs with a real SqlTransaction) when only a participant is registered.

🤖 Generated with Claude Code

…nts pending

DocumentSessionBase.SaveChangesAsync used to early-return when
_workTracker.HasOutstandingWork() returned false. _workTracker tracks document
operations and event streams - it does NOT include ITransactionParticipants
registered via AddTransactionParticipant. A session that has only registered
participants (no doc ops, no streams) therefore had SaveChangesAsync skip the
entire pipeline - the SQL transaction was never begun, and every queued
participant's BeforeCommitAsync silently never ran.

Wolverine's GH-2941 surfaced this: Wolverine.Polecat's outbox path for scheduled
cascades (DeliveryMessage<T>.DelayedFor(...) emitted by a [ReadAggregate] or
[DocumentExists]-decorated handler with no document writes) hits
Session.StoreIncoming(...), which registers a StoreIncomingEnvelopeParticipant
whose BeforeCommitAsync inserts the scheduled envelope row into
wolverine_incoming_envelopes. With no document ops on the session, that
participant never ran and the scheduled message was silently lost.

Fix: extend the early-return guard to also account for queued transaction
participants. Listeners and other commit-time side effects continue to run via
the normal pipeline once the transaction is begun.

New test: Bug_save_changes_runs_participants_only pins the contract that
ITransactionParticipant.BeforeCommitAsync runs (and runs with a real
SqlTransaction) when only a participant is registered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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