Skip to content

Wolverine 6.24.1

Choose a tag to compare

@jeremydmiller jeremydmiller released this 30 Jul 03:15
2a3761d

Patch release. Four reported issues, all with reproductions from production clusters.

Fixes

#3701wolverine_node_records grows without bound (#3734)

A reporting cluster reached 36,135,221 rows / 16 GB in five days on a diagnostic table nothing on the hot path reads. Three distinct defects:

  • INodeAgentPersistence.DeleteOldNodeRecordsAsync was implemented for every relational store and never invoked outside tests.
  • The pruning that did run bounds the table by age only (NodeEventRecordExpirationTime, 5 days), which is no ceiling at all at high write rates — every one of those 36M rows was inside the window.
  • That age sweep's hourly throttle was dead. Its backing field was never assigned (the CS0649 suppression on it said so), so a full-table delete went out on every recovery cycle — every 5 seconds by default.

New Durability.NodeRecordRetention (default 10,000 rows) and Durability.NodeRecordPruningPeriod (default hourly). MultiTenantedMessageStore now delegates the trim to the main store instead of inheriting a no-op default, and Sqlite, MySQL and Oracle gained implementations they had also been missing.

#3697 — no supported force-catch-up under Wolverine-managed event subscription distribution (#3735)

Wolverine already implemented the coordinator-driven catch-up path, but only exposed it as a TrackActivity() stage. Adds the standalone entry point on IHost and IServiceProvider, plus <T> ancillary-store variants:

await host.PauseThenCatchUpOnMartenDaemonActivityAsync();
await host.PauseThenCatchUpOnMartenDaemonActivityAsync(CatchUpMode.AndDoNothing);
await host.PauseThenCatchUpOnMartenDaemonActivityAsync<IMyStore>();

It never calls IProjectionDaemon.CatchUpAsync — doing so under a live coordinator is what produces the ProgressionProgressOutOfOrderException and pk_mt_event_progression duplicate-key errors suites have been retrying around. Resuming the agents that already own the shards means there is only ever one writer.

#3733 — a comma in an agent Uri voided a whole batch confirmation (#3736)

AgentsStarted, StartAgents, AgentsStopped and StopAgents joined their Uri[] on a comma, which RFC 3986 permits unescaped in a path segment. Agent URIs embed tenant ids and projection names, so one comma shattered an agent into fragments — and because the read side built the array in a single projection, the resulting throw took out the confirmation for the entire batch. Newline is the delimiter now, and entries are parsed individually so a bad one names itself.

The comma remains the default on the wire for payloads that do not contain one, so rolling upgrades keep working in both directions.

#3706 — RabbitMQ acks were cumulative (#3737)

Every ack went out as BasicAckAsync(tag, multiple: true), acknowledging every lower delivery tag on the channel. That is only correct when completions happen in delivery order, and they do not with ConsumerDispatchConcurrency > 1 — acking one message silently acknowledged deliveries whose handlers were still running, and a crash at that moment lost them.

Acks are now per message. Two dead-letter paths that relied on the cumulative sweep settle themselves, most importantly the un-mappable-message branch in WorkerQueueMessageConsumer, which dead-lettered and returned without touching the delivery at all. This unblocks the planned native-ack parallel endpoint mode.

Also included

  • #3730 — compliance coverage for a pause and node loss landing on in-flight assignments (GH-3698)
  • #3732 — seed the departed node's inbox rows as already owned (GH-3729)