Repository navigation
Wolverine 6.41.0
28 commits since V6.40.0. Minor rather than patch because of the new EF Core and topology features below — and because one change is breaking.
⚠️ Breaking change
AutoApplyTransactions() now fails the chain build when two persistence providers can own it (#4631). A handler taking both a DbContext and a Marten/Polecat/Fisher IDocumentSession matched two providers, and AutoApplyTransactions applied neither — no transaction support, no warning, no log line, and whatever the un-owned store buffered was discarded when the scope closed. [Transactional] on the same handler took the first ordered provider instead, so the document session was never saved. Both outcomes were silent and the handler reported success.
This stops an application that starts today. One that writes through two stores from a single handler has been losing one of those writes all along; it will now refuse to build until the handler says which store owns the transaction. Add [Transactional(typeof(X))] or [Storage(typeof(X))], or [NonTransactional] to opt out. Two-provider atomicity remains unsupported — the designated store commits, the other does not. Saga chains are exempt.
Entity Framework Core
- Lightweight mode has a real outbox (#4630). Cascaded messages from a
Lightweightmessage handler now enlist in the sameSaveChangesAsync()as the business write, andPublishDomainEventsFromEntityFrameworkCore()— inert inLightweightbefore — runs on every path. - Declarative
EfCoreOpsset-based operations (#4629), which force the eager transaction they need rather than failing quietly. - Scraped domain-event envelopes are flushed before the Eager commit (#4628).
- Storage actions and conjoined tenancy mean what they say (#4611, #4612, #4613). Declaring a return type as
Update<T>orStore<T>rather thanIStorageAction<T>used to silently save nothing. - The conjoined tenant filter composes with your own query filter (#4632) instead of replacing it — EF 9 discarded the second filter, EF 10 throws.
- Four coverage gaps closed (#4635), including the first tests for a non-flat EF model.
Multi-tenancy
Envelope.TenantIdis normalised throughTenantIdStyle(#4640).MessageContext.TenantIdalways applied the style andEnvelope.TenantIdnever did, so underForceLowerCasea handler saw"RED"on the envelope beside"red"on the context. Polecat and Fisher read the envelope, so they wrote the raw casing — invisible on SQL Server's case-insensitive collation, and on Fisher's BINARY collation the row was unreachable.- A conjoined tenancy battery that runs against Marten, Polecat and Fisher (#4634) — which is what exposed the above.
- Fisher's
OutboxedSessionFactoryresolves theMainstore lazily (#4633).
Transports and topology
- A partitioned topology can declare its own grouping rules, additively rather than authoritatively (#4616) — thanks @erdtsieck.
- Google Pub/Sub shares one subscription across nodes by default (#4615), rather than a subscription per node.
- Kafka replay imitates the endpoint that really listens (#4620), taking the replay group from a
ListenToKafkaTopicsgroup and stamping the topic asDestination— thanks @lahma.
Durability and correctness
EnqueueDirectlyAsyncretires the inbox row it forwards (#4645). A delayed message sent from a handler to a database-backed queue that another node owns was silently destroyed: the forwarding node left anIncomingrow behind, and the owning node's anti-duplicate probe then deleted the queue row it had just written. Nothing logged, nothing dead-lettered. Reported with a full four-step reconstruction by @smoqmilus.EndpointCollection._listenersis no longer a plainDictionary(#4646). A failover handing one node several exclusive listener agents at once had up to ten threads writing it while every send read it; aDictionarycorrupted mid-resize is poisoned permanently, and the node never routed again until the pod was replaced. Also reported by @smoqmilus.- Messages no longer inherit a stale ambient
Activity. Recurring occurrences (#4647), inline receivers (#4649) and the agent plumbing loops (#4650) were parenting every message under whateverActivity.Currenthappened to be live when the agent or listener started. - A handler chain resolves its pre-generated type by full name (#4625) — thanks @skoging.
- Batching honors a batch handler registered with
AddMessageHandler.
Also
- Upgraded to Weasel 9.35.1, finishing the batched-query registration (#4624).
- Fixed a CritterWatch link in the RabbitMQ dead letter queues guide — thanks @jakobt.
Contributors
Thanks to @erdtsieck, @jakobt, @lahma, @skoging and @smoqmilus.