Skip to content

Wolverine 6.24.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 28 Jul 20:31
bd019db

Two data-loss fixes — but for unusual usages

This release closes two bugs that silently destroyed data rather than failing loudly. Both are worth reading before you skip the rest of these notes.

Durable inbox rows were orphaned when a circuit breaker tripped (#3680). DurableReceiver checked its latched flag before calling MarkReceived. The latched path still persists each envelope to the inbox as a safety net — but on an envelope that never went through MarkReceived, Status is the enum default (Outgoing) and Destination is null. Both are filter columns for inbox recovery, so the rows were written in a state no recovery sweep on any node could ever see. The null Listener also skipped the nack back to the broker, and the broker's redelivery after restart hit DuplicateIncomingEnvelopeException — which acks and drops. Net result: genuine message loss under a durable inbox any time a circuit breaker trip latched the receiver mid-flight. Measured on the circuit-breaker suite, 9 of 1,200 messages were lost per run.

Dropping one tenant from a shared partition bucket destroyed its co-tenants' data (#3686). Found alongside #3683. Tenant bucketing — registering several small tenants against one partition suffix so they share a physical partition — is documented and exposed through PartitionPerTenant(p => p.AllowPartitionSharing = true), and it did not work on either engine. It had no test coverage, because the doc sample demonstrating it is compile-only and never executed.

Global partitioning

Part of the GlobalPartitioning epic (#3482).

  • Global partitioning topologies for PostgreSQL and SQL Server queues (#3468, #3469)
  • End-to-end sharded-processing suites for Azure Service Bus, GCP Pub/Sub, NATS, Redis Streams and Pulsar (#3467). The scenario is lifted into Wolverine.ComplianceTests.Partitioning.ShardedProcessing, so a new transport costs one small test class
  • Native-mode design comparison and per-transport native alternatives documented (#3481)

The new suites immediately found two real bugs:

  • NATS global partitioning had never worked at all. The topology forces EndpointMode.Durable on every slot, and a NatsEndpoint only supports Durable when JetStream-backed — so every UseShardedNatsSubjects() call threw at configuration time. The topology now enables JetStream on its own endpoints and declares a work-queue stream per shard, without which the listener died at startup on stream not found
  • Pulsar named its companion local queues off the full topic path, producing queues like global-persistent://public/default/orders1. They now use the topic's short name, matching every other transport

Multi-tenancy and persistence

  • EF Core tenant partition back-fill (#3496). Routine migration deltas deliberately leave Weasel-managed partitions alone, so a table joining an existing managed set — a newly deployed service, or a newly mapped ITenanted entity — had no partition for any tenant registered before that table existed. IConjoinedTenantPartitions<T>.MigrateTenantPartitionsAsync() reconciles every partitioned table against the full registered tenant set, with per-table TenantPartitionResult reporting
  • Conjoined tenant partition bucketing actually works now, on both PostgreSQL and SQL Server (#3683, and see #3686 above)
  • Exclusive listener inbox recovery is now covered for RavenDb (#3595) and CosmosDb (#3596)

Transports

  • RabbitMQ: deliveries are settled against the channel they arrived on (#3687). Acking a delivery on a torn-down channel threw a NullReferenceException
  • NATS: auto-provisioned JetStream durable consumers are filtered to their own subject (#3676). FilterSubject was only assigned when ConsumerName was empty, so every durable consumer on a stream received every message. The fix needs a FilterSubjects multi-filter — a single filter cannot cover both {subject} and {subject}.scheduled, and a work-queue stream discards an uncovered control message
  • MQTT: the v5 authentication method name is configurable (#3588). It was hardcoded to "OAUTH2-JWT". Azure Event Grid's custom JWT authentication requires CUSTOM-JWT, so those brokers could not be reached through Wolverine's authentication support at all. You could already set the method by hand through MqttClientOptionsBuilder.WithAuthentication(), but that gave up Wolverine's token refresh loop — the whole reason to use MqttJwtAuthenticationOptions. You no longer have to choose
  • The HTTP transport can send to a destination nobody pre-registered (#3681, reported as ProductSupport#34). WolverineHttpTransportClient used the endpoint's OutboundUri purely as an IHttpClientFactory client name, then posted to that client's BaseAddress — so operator commands sent back over the HTTP transport failed with An invalid request URI was provided

Performance

  • RabbitMQ consumer dispatch concurrency is now per-endpoint (#3492). The client default of 1 was the bottleneck. Simulated handler, 2,000 msg/s offered load, 30s measured window:

    ConsumerDispatchConcurrency Throughput Transit p50
    1 (client default) 163.7/s — (nothing from the measured window was consumed before the run ended)
    5 828/s 22,871.9 ms
    20 1,999.1/s 1.486 ms (p95 2.54, p99 3.22)

    The 5.1x and 12.2x multiples understate it — at 1 and 5 the listener never catches up at all.

  • Amazon SQS batches message deletions and chunks outgoing batches on the 256KB request size limit (#3493)

  • Azure Service Bus session listeners are no longer quadratic — the n² session loops are now n. MaxConcurrentCalls is surfaced, and a batched defer settles the original message (#3494)

HTTP and gRPC

  • codegen write emitted an empty file — a bare namespace with no type — for every gRPC chain (#3692), which broke proto-first codegen

Fixes from the community

  • CosmosDb: removed a spurious CancellationToken dependency from TransactionalFrame (#3685)

Dependencies

  • Weasel 9.19.0 → 9.20.0 (required by the partition bucketing fix; the root cause was in Weasel, fixed by JasperFx/weasel#392)
  • No JasperFx, Marten or Polecat bumps this cycle

Contributors

Thank you to everyone who contributed to this release:

Full Changelog: V6.23.1...V6.24.0