Repository navigation
Wolverine 6.46.0 is the release where Native AOT stopped being aspirational. Ten of the changes below came out of one sustained hunt, and the pattern that emerged is worth stating plainly: under Native AOT, the fix is almost never to root a generic harder — it is to stop closing one.
Thank you to the people outside the core team who shipped code in this one. Five of the fixes here came from the community, and several of them are in corners the maintainers would not have found on their own.
Community contributions
- @ayuksekkaya — two separate fixes, both from real usage.
[FromClaim]code generation was broken for URI claim types (#4783), and EF Core had three distinct problems in one report:[Entity]on a step method, query plans, and multi-tenantDbContextresolution (#4769). - @alesdvorakcz — a handled inbox row now gives up its owner (#4775, GH-4739). This is the kind of durability bug that only shows up under real load, and the fix is in the hot path.
- @uniquelau — a fallback authorization policy now counts in the anonymous
User-scope warning (#4779), plus a documentation fix for something that bites every newcomer: the first-use handler compile counts against a tracked session's timeout (#4786). - @erdtsieck —
[Deduplicated]claims are kept in the main store when you run a database per tenant (#4813). Every[Deduplicated]chain used to throw on its first keyed message in that configuration.
Native AOT
Sagas work under Native AOT now (#4809). That took three separate blockers, and each one taught us a rule that is now written down:
[DynamicDependency]preserves metadata, not code. An AOT root over a value-type instantiation is silently ineffective — no warning, no diagnostic, just a missing type at startup. The roots that appear to work survive through reference-type canonical sharing, which is why this went unnoticed for so long.- A generic virtual method cannot be rooted at all. The fix is a factory supplied by generated code.
- A reflectively-closed frame needs a root only if a policy places it. A frame built during code generation is both unreachable in a native image and uncollectable by the hook — so half the places we thought needed attention never did.
Acting on those: a frame can now contribute its own AOT root (#4801), the EF Core frames closed over your DbContext are rooted (#4803), and the Marten, Polecat and Fisher FetchLatest frames were de-genericized rather than rooted (#4793, #4806). The HTTP and response-aware chain paths got the closed generics they need (#4790), and an [UnconditionalSuppressMessage] scope that ILC rejects outright was fixed (#4791).
Two things worth calling out for anyone running AOT in production:
- The partitioning and deduplication rules closed a generic over a value type at startup — grouping by a
Guidtenant id, or a[DeduplicationIdentity]on aGuid, which needs no configuration at all. Both crashed the host at startup in a native image. Neither was reachable by any existing rooting mechanism, so both now avoid closing the generic at all. - There is also an honest piece of bookkeeping in this release: an audit of all 284 trim/AOT suppressions in core and the relational stores found that 62 of the 89 that justify themselves with a reachability claim are wrong — the member does run in a native image. Most are safe anyway, for reasons unrelated to what they claim. The measurement is checked in, because the stated reason being wrong is exactly what kept this class of bug invisible.
Durability and clustered agents
A wave of fixes to inbox ownership and global partition slots, all of them cases where a message could stall rather than fail:
- A global partition's companion queue is now recovered on the slot's owner (#4794) and drained when the slot moves (#4795).
- The inbox release is held until in-flight handlers finish (#4799), and a bounded drain that gives up on them now says so (#4798).
- CosmosDB and RavenDB release the owner when they mark an inbox document handled (#4792).
- Solo mode honors a paused agent restriction (#4800).
- A message arriving over the HTTP transport used to be stored in the durable inbox under an address nothing was listening on, so a replayed dead letter — or the messages of a node that died mid-handler — were skipped on every durability pass, silently, forever (#4814). The skip itself was unlogged, which is why it took a bug report; it logs now.
- MQTT could lose a persistent session's backlog outright. The broker starts flushing queued messages the moment the connection comes up, before Wolverine has registered its listeners, and there was no receive handler attached at all in the earliest part of that window — so MQTTnet acknowledged those messages and dropped them with no exception, no dead letter, and not even a log line. Worse, a resolution miss was cached, so one early message poisoned that topic for the life of the process.
- MQTT's broker-per-tenant sender was still fire-and-forget under a durable outbox: the send never threw, the outbox deleted its row believing it had succeeded, and a restart lost the message. Fixing it needed a new seam in core, because
TenantedSenderdeliberately cannot forward anISenderCallback— doing so broke fire-and-forget senders in a previous attempt. There is now aCallbackAwareTenantedSenderfor the senders that settle the outbox themselves, with the original left exactly as it was. Every broker-per-tenant transport can use it; only MQTT does so far.
Security and dependencies
- The vulnerable
System.Formats.Asn17.0.0 is lifted, and the whole package graph is audited (#4781, GH-4773). - Roslyn pins are scoped to the TFMs that actually reference them (#4780, GH-4768).
- Marten 9.46.0, with the JasperFx 2.80.2, Weasel 9.41.0 and Fisher 1.19.0 versions that move with it. Fisher advances deliberately: an added member on a
JasperFx.Eventsinterface breaks a store at runtime rather than at compile time, so leaving a store package behind is the actual hazard.