Skip to content

Wolverine 6.41.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 28 Sep 01:29
d00a3ed

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 Lightweight message handler now enlist in the same SaveChangesAsync() as the business write, and PublishDomainEventsFromEntityFrameworkCore() — inert in Lightweight before — runs on every path.
  • Declarative EfCoreOps set-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> or Store<T> rather than IStorageAction<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.TenantId is normalised through TenantIdStyle (#4640). MessageContext.TenantId always applied the style and Envelope.TenantId never did, so under ForceLowerCase a 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 OutboxedSessionFactory resolves the Main store 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 ListenToKafkaTopics group and stamping the topic as Destination — thanks @lahma.

Durability and correctness

  • EnqueueDirectlyAsync retires 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 an Incoming row 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._listeners is no longer a plain Dictionary (#4646). A failover handing one node several exclusive listener agents at once had up to ten threads writing it while every send read it; a Dictionary corrupted 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 whatever Activity.Current happened 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.