Skip to content

Wolverine 6.50.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 14 Oct 01:50
55a4625

Wolverine 6.50.0 is a feature release. The headline is a new way to send a message to every node in your cluster. Just as much of this release came from the community, though: five community pull requests landed, and several issue reports led straight to fixes.

The two Bobcat packages, WolverineFx.Bobcat and WolverineFx.Bobcat.Http, ship as 6.50.0-alpha.1 and are still pre-release.

Broadcast a message to every node in the cluster

  • BroadcastToAllNodesAsync() (#4993, closes #4988). Wolverine's normal delivery is competing consumers, where exactly one node handles a message. Sometimes you want every node to hear about something instead: a cache to invalidate, a configuration change to pick up, some node-local state to reset. Now you can just do this:

    await bus.BroadcastToAllNodesAsync(new InvalidateCache("products"));

    Every running node, including the one that sent it, handles the message exactly once with its own handler. The sending node gets the message on the local routes its type already has, or on local://default, and it's never forwarded to the external routes PublishAsync() would use. Every other node gets a copy on its node control endpoint, which is the broker control queue if you've configured one and the database control queue otherwise. Nodes that have gone stale are skipped, using the same rule the leader applies before ejecting a node. A broadcast from inside a handler goes through the outbox like any other outgoing message, and correlation ids are stamped as usual. In Solo and Serverless durability modes the message is delivered to this node only, and MediatorOnly refuses it just like PublishAsync(). It's an extension method on IMessageBus, so no other IMessageBus implementation breaks, and TestMessageContext records broadcasts in a new BroadcastToAllNodes list for your unit tests. See the new Broadcast Messages to Every Node guide.

From the community

A big thank you to everyone who sent pull requests and reports for this release:

  • @jcd1991 contributed two PostgreSQL transport features:

    • Opt-in LISTEN/NOTIFY wakeups for PostgreSQL queues (#4998, closes #3480). With EnableQueueNotifications(), an idle queue listener is woken as soon as a message is written instead of waiting out its polling interval. In local benchmarks the median delivery time on an idle queue dropped from about 650ms to under 3ms. It's off by default because it holds one extra, long-lived database connection per database per running process. Read the trade-offs in the new Queue Notifications with LISTEN/NOTIFY section before you turn it on.
    • FIFO ordering for sharded PostgreSQL queue batches (#4970, closes #4862, reported by @alexandrefresnais). Messages for one group id that went through a PostgreSQL shard queue could run out of order. EnableDeterministicQueueOrdering() now preserves their order, and it was followed up for SQL Server, MySQL, Oracle and SQLite in #4987.
  • @jcd1991 also fixed Wolverine.HTTP treating a container-registered service as the JSON request body on POST and PUT endpoints (#5010, closes #3863, reported by @erdtsieck). The same parameter on a GET endpoint was correctly injected, but on a POST or PUT every call failed with a 500. An explicitly registered, non-keyed concrete type is now resolved from the container on every endpoint. This is a behavior change: if you register a type in the container and post it as a request body without [FromBody], add [FromBody] to keep binding it from the request.

  • @erdtsieck found and fixed a ping-pong in agent assignment during blue/green warm-ups (#5000, closes #4999). Projection partitions that could only run on the previous version's nodes moved between those nodes on every evaluation, which showed up in production as 1,000 to 3,000 ReassignAgent commands a minute. A partition that fits nowhere now stays where it is unless another node is strictly lighter. The follow-up in #5001 makes it settle in one move.

  • @PascalEschbach contributed a big round of NATS follow-ups (#4976, closes #4973), with tests for every change:

    • one schedule subject per message, with listeners that skip schedule control messages;
    • resources setup provisioning declared streams once per connection;
    • per-tenant connection configuration, tenant subject mappers, and a new TrustTenantIdHeader setting;
    • safer dead letter forwarding that only terminates the original after a confirmed forward;
    • a deadline on every JetStream publish;
    • named consumers for dotted subjects;
    • NATS logging through the host's ILoggerFactory;
    • a health check that reports a failed listener start.

    #4985 followed up on the core side.

  • @Maflend reported that the static handler registry ignored custom discovery rules once conventional discovery was disabled (#4984), a regression since 6.44.0. It's fixed in #4992.

  • @uniquelau's report in marten#5652 led to #5011, below: the leader no longer builds every event store's full usage descriptor on every heartbeat.

Agents and event subscriptions

  • The leader's heartbeat no longer builds the full event store descriptor (#5011, closes #5002). Assigning event subscription agents only needs each store's async shard names and its database topology. Wolverine now reads those through JasperFx 2.87.1's IEventStore.RegisteredShardNames() and the new IEventStore.TryDescribeDatabasesAsync(), instead of a full TryCreateUsage() that also read the highest event sequence and described every projection, subscription and event type, for every store, every 10 seconds. To get the full saving, also take Marten 9.50.1, Polecat 5.40.1 or Fisher 1.20.2, which answer TryDescribeDatabasesAsync() straight from their tenancy.
  • A projection rebuild always hands its shard back to continuous execution (#5008, closes #5003). A rebuild that threw used to leave the shard stopped, for good if it had already paused on a transient failure. The registered-projection rebuild path that CritterWatch uses never resumed the shard at all. Both paths now resume it whether the rebuild succeeded or failed. The health-check sweep also leaves a shard alone while its projection is being rebuilt, using JasperFx's new IProjectionDaemon.IsRebuilding(), so it can no longer start a second copy of the shard on top of a running rebuild.

Transports and persistence

  • SQL Server queue tables can switch between the default layout and OptimizeQueueThroughput() (#4991, closes #4989).
  • EnableDeterministicQueueOrdering() for SQL Server, MySQL, Oracle and SQLite (#4987).

HTTP

  • Wolverine.Http binds single-Value strong-typed id wrappers, such as record struct OrderId(Guid Value), without a TryParse (#4995, closes #4978).

Runtime

  • The first host in a process pins the assembly that registered it, not the one that called Build() (#4996). A host built by a shared test helper in another assembly could make later hosts in the same process scan the helper's assembly for handlers.
  • EndpointName is a typed field on EndpointDescriptor (#4997, closes #4887). This drops the old EndpointName properties row, so monitoring tools that read it should move to the field.

Event Modeling and Bobcat

  • describe-event-model --specs and event-model --specs link a model's slices to their Bobcat specifications (#5006, closes #5004).
  • ThenEventuallyCaused() works with a database per tenant (#5009, closes #4962). It waits on and reads the act's tenant's own database, and an act with no tenant is looked for in every database.
  • WolverineFx.Bobcat moves to Bobcat 0.60.4 and JasperFx 2.85.1 (#4968, #4972, #4980 closes #4975, #4990), and the whole repository moves to xUnit v3 4.x on Microsoft.Testing.Platform 2 (#4983, closes #4979).

Dependencies

  • JasperFx 2.87.1, Marten 9.49.0, Polecat 5.38.0, Fisher 1.20.1 and Weasel 9.45.0. Applications can take Marten 9.50.1, Polecat 5.40.1 and Fisher 1.20.2 directly.