Skip to content

Fix durable inbox to mark envelopes handled atomically with ancillary stores#2394

Merged
jeremydmiller merged 1 commit intomainfrom
fix-ancillary-store-inbox
Mar 31, 2026
Merged

Fix durable inbox to mark envelopes handled atomically with ancillary stores#2394
jeremydmiller merged 1 commit intomainfrom
fix-ancillary-store-inbox

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Fixes #2382

When a handler targets an ancillary [MartenStore], incoming envelopes were persisted in the main store's inbox but the handler's transaction committed against the ancillary store. The FlushOutgoingMessagesOnCommit listener either skipped the mark-as-handled update (same database, different schema) or couldn't reach the correct table (different database), leaving envelopes stuck as Incoming.

Changes

File Change
IChain.cs Added AncillaryStoreType property for handler chains to declare their target store
Chain.cs Implemented the property
MartenStoreAttribute.cs Sets chain.AncillaryStoreType during Modify()
MessageStoreCollection.cs Memoizes message-type → ancillary store mapping at startup
WolverineRuntime.HostService.cs Scans handler chains at startup to populate the mapping
DurableReceiver.cs Sets envelope.Store before StoreIncomingAsync for ancillary store routing
FlushOutgoingMessagesOnCommit.cs Resolves correct incoming table name for same-database ancillary stores

How it works

  1. At startup, handler chains with [MartenStore] are scanned and a messageTypeName → IMessageStore mapping is built
  2. When DurableReceiver receives an envelope, it checks this mapping and sets envelope.Store to the ancillary store
  3. DelegatingMessageInbox routes StoreIncomingAsync to the correct store based on envelope.Store
  4. FlushOutgoingMessagesOnCommit updates the correct incoming table within the handler's Marten transaction

For same-database setups (different schemas), the mark-as-handled SQL targets the main store's incoming table within the ancillary store's Marten session transaction, achieving atomicity.

For different-database setups, the envelope is persisted directly in the ancillary store's inbox, so both the handler's side effects and the envelope status update are in the same database transaction.

Test plan

  • 4 new tests pass (ancillary inbox routing, main store routing, mixed messages, handler side effects)
  • Aggregate handler workflow: 17/17 passed
  • Durability tests: 15/15 passed

🤖 Generated with Claude Code

… stores

When a handler targets an ancillary MartenStore, the incoming envelope
was persisted in the main store's inbox but the handler's transaction
committed against the ancillary store. The envelope status update
was either skipped or targeted the wrong schema, leaving envelopes
stuck as Incoming.

Changes:
- IChain.AncillaryStoreType property tracks which ancillary store a
  handler chain targets (set by MartenStoreAttribute.Modify)
- MessageStoreCollection memoizes message-type-to-ancillary-store
  mapping at startup for fast lookup
- DurableReceiver sets envelope.Store before StoreIncomingAsync so
  DelegatingMessageInbox routes to the correct store
- FlushOutgoingMessagesOnCommit resolves the correct incoming table
  name for same-database ancillary stores instead of skipping the
  mark-as-handled update

Fixes #2382

Co-Authored-By: Claude Opus 4.6 (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.

Durable receiver should store envelopes in the ancillary store's database/schema when handler targets an ancillary MartenStore

1 participant