Bump WolverineFx and WolverineFx.Http - #27
Conversation
Bumps WolverineFx from 6.17.2 to 6.23.1 Bumps WolverineFx.Http from 6.17.2 to 6.23.1 --- updated-dependencies: - dependency-name: WolverineFx dependency-version: 6.23.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: WolverineFx.Http dependency-version: 6.23.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
WolverineFx/.Http/.Marten/.RabbitMQ/.RuntimeCompilation to 6.23.1, Marten/ Marten.AspNetCore to 9.20.1 - applied as one manual bump instead of merging Dependabot PRs #24/#27/#28/#29/#30 individually, since those packages release in lockstep (per this file's own existing comment) and the 5 PRs disagreed with each other on shared version lines (#24 vs #28 both touched Marten to different targets; #27/#29/#30 all touched WolverineFx independently). Verified: solution builds clean, all 383 non-integration tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Superseded by e3f9593 — WolverineFx/.Http/.Marten/.RabbitMQ/.RuntimeCompilation and Marten/Marten.AspNetCore are consolidated into one manual bump instead of merging this PR individually, since these packages release in lockstep and several of these Dependabot PRs disagreed with each other on the same version lines (e.g. this PR vs #24/#28 both touching Marten to different targets, or #27/#29/#30 all touching WolverineFx independently). Verified: build clean, all 383 non-integration tests pass. |
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. You can also ignore all major, minor, or patch releases for a dependency by adding an If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
Updated WolverineFx from 6.17.2 to 6.23.1.
Release notes
Sourced from WolverineFx's releases.
6.23.1
Agent distribution
TL;DR: if you pause a projection from CritterWatch on 6.23.0, restarting it appears to do nothing for a full minute. This fixes that.
A paused projection or subscription agent now resumes immediately when you restart it (#3663). On 6.23.0 the restart was accepted, the pause restriction was cleared, and then nothing happened until the pending-assignment ledger's TTL expired — 2 ×
CheckAssignmentPeriod, so 60 seconds with the defaults. Long enough that an operator reasonably concludes the agent is never coming back.The ledger introduced in 6.23.0 (#3622) only counted an assignment as confirmed if a later evaluation saw the agent running and still assigned to the same node. A pause makes those two conditions mutually exclusive: the first evaluation that can observe the delivered assignment is the same one that detaches the agent. The entry was never confirmed, nothing on the stop path cleared it, and the restart's
AssignAgentwas suppressed as a duplicate still in flight. Delivery alone now confirms the entry, which is the only question the ledger was ever asking.Pausing an agent no longer briefly starts it first (#3666).
ApplyRestrictionsAsynckickstarted a health check before persisting the operator's restriction change, so that evaluation ran against the old restrictions and could act against the very intent being applied — for a pause, re-assigning and starting the agent one beat before the merged evaluation stopped it again. Besides the wasted daemon start/stop cycle, this is what armed the stale ledger entry behind #3663.PostgreSQL
Advisory-lock sessions stay invisible to Marten's async-daemon gap detection (#3664). Marten 9.16.1+ will not skip a stale event-sequence gap while any session whose open transaction predates that gap is still alive (marten#4953). A session parked in an open transaction for the life of the process therefore reads as a permanent "possible reserver" and can hold the high-water mark — and every async projection — behind a gap that is genuinely dead.
Wolverine's long-held locks were already shaped correctly: leader election and node coordination hold session-scoped advisory locks on a dedicated connection with no transaction, so they show up as
state='idle'with a NULLxact_start. Those sessions are now also taggedapplication_name = 'wolverine-advisory-lock:<database>', which turns apg_stat_activityinvestigation from guesswork into something you can read at a glance. The constraints are pinned in tests and in the Postgres durability docs, including the trap worth knowing in your own code: never add a keepalive query inside a long-lived open transaction — it bumpsstate_change, makes the session look active, and re-promotes it to candidate reserver.Note for combined Marten + Wolverine deployments: older guidance suggested Postgres's
idle_in_transaction_session_timeoutas a dead-gap backstop. Prefer upgrading Marten and usingSkipStaleGapsDespiteLiveTransactionsAfterinstead.6.23.0
Projection & agent distribution
TL;DR: this prevents Wolverine from going into a panic doing agent assignments and churning crazily hard during Kubernetes rollouts or cluster starts and that's a very good thing
The bulk of this release. A wave of fixes (WO-1..8) to the agent assignment plane that together remove the re-assignment churn and livelock that could leave projection agents flapping or wedged.
AssignAgentfloods (#3622)IEventSubscriptionAgent.Failuresurfaces aShardFailure— category, the failing event's sequence and type, and the root exception type — through health checks and a newIWolverineObserver.AgentPausedhook plus aNodeRecordType.AgentPausedrecord. Failures bound to a specific event (ApplyEvent,EventSerialization,UnknownEventType) or to two processes racing one shard (ProgressionOutOfOrder) are no longer auto-restarted, since they would die on the identical event every time (#3637, #3638)Durability.AgentStartRetryAttempts/AgentStartRetryDelay, instead of idling a fullCheckAssignmentPeriodafter a startup race (#3519)FindAgentUriAsyncoverload (#3647) and a store-awareTryRebuildRegisteredProjectionAsyncoverload (#3618)Bumps JasperFx.Events to 2.36.1, Marten to 9.20.0 and Polecat to 5.7.0.
HTTP
Content-Typeon an[AcceptsContentType]route returns 415 rather than 404 (#3649)[AsParameters]endpoints no longer advertise a form body, which had been dropping them from route matching entirely (#3630)PublishMessage<T>andSendMessage<T>— now describe the message they read from the body. They had been advertising no request body at all in OpenAPI (#3646)Transports
ListenToPubsubSubscriptionOnNamedBroker(#3631)EnvelopeMapperreads both timestamp header formats (#3645)EnclosedMessageTypesheader is split before the message type is resolved, so interop works across Azure Service Bus, SNS, SQS and the database transports (#3628)Persistence
DbBatch, so the work splits per statement.Other
Contributors
Thank you to everyone who contributed to this release:
... (truncated)
6.22.0
Wolverine 6.22.0 rolls up the claim-check backend wave, distributed-agent and durability hardening, HTTP/OpenAPI binding fixes, and the Marten 9.18 / JasperFx 2.34 critter-stack alignment.
Dependency alignment
Marten.AspNetCore/Marten.Newtonsoft), Polecat[5.5.0,6.0.0)Claim-check offloading
IClaimCheckStore(#3564)Distributed agents & durability
IHost.ClearAllWolverineStorageAsync()(#3592)EventSubscriptionAgentrestores continuous execution after Rebuild/Rewind (#3520)running_on_node, marten#5001) (#3578)HTTP & OpenAPI
[FromQuery]on arrays/collections instead of misrouting to complex-flattening (#3602)[FromQuery] decimalcorrectly + shape-test OpenAPI parameter description across type families (#3586)[FromQuery]/[FromHeader]parameter twice in OpenAPI (#3586)StreamPaged,StreamPagedByCursor, ETag support (#3593)DbContextparameter as the HTTP request body (#3538)Transports
WolverineFx.Mqtt5package (MQTTnet 5) (#3517)Persistence
RAW(16)Guid correctly (#3581)... (truncated)
6.21.0
Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.
Conjoined multi-tenancy for EF Core (#3465)
Mark an EF Core entity with
ITenanted(the marker shared critter-stack-wide fromJasperFx.MultiTenancy) and register yourDbContextwithAddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mappedtenant_idcolumn, a tenant-bound global query filter you can't forget, stamp-on-insert,CrossTenantWriteExceptionon cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritativewolverine_tenantsregistry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the newConjoinedMultiTenantedEfCoresample app and docs.Messaging performance (GH-3490 / GH-3492)
A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):
(100, 250ms)settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).MaximumMessagesToReceivelistener knob on both (default 100;1restores strict message-at-a-time persistence).Envelope[]arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.SendMessageBatchper-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).BufferedInMemory()/ListenerCount()with measured numbers, and a matching page exists for Kafka.Behavior changes to note
Debug(wasInformation) — restore withopts.Policies.MessageSuccessLogLevel(LogLevel.Information).wolverine-execution-timeis now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).Transports and messaging
PrefetchCounton listeners and transport defaults (GH-3471)DelaySecondsfor short scheduled sends on standard queues (GH-3472)DeliverAt(GH-3470)HTTP / gRPC
IAsyncEnumerable<TRequest> -> Task<TResponse>handler shape (#3500)StreamAsync<TRequest, TResponse>overload (#3459)Dependencies
JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.
6.20.0
Wolverine 6.20.0
Dependency upgrades (critter stack)
Multi-tenancy & connection footprint
nodes × databases. Plus daemon tracker-subscription leak hygiene.IntegrateWithWolverine()now honors a database-per-tenant Polecat store and readsMainDatabaseConnectionString.DatabaseDescriptor.Portinstead of re-parsing (jasperfx#514).CritterWatch / connection state
Kafka
SentAtand expose record headers on raw-JSON listeners (GH-3407).JsonSerializerOptionson raw-JSON endpoints now actually applies;PublishRawJsonmapper registration fixed.Sagas
SagaConcurrencyExceptionnow inheritsJasperFx.ConcurrencyException(GH-3444) — existingOnException<ConcurrencyException>()policies now catch saga concurrency failures.Other
TrackedSessionnot-tracked vs not-routed fix (#3435).6.19.0
CosmosDB
CosmosDbConfiguration.PartitionSagasById(): opt-in, saga id becomes the document partition key (GH-3415) @mysticmindCosmosClientwhose serializer would drop a saga'sidat host start; document the camelCase requirement (GH-3416) @mysticmindHTTP / OpenAPI
uuidinstead of falling back tostring(GH-3420) @mysticmindDurability / persistence
IWolverineObserver.ConnectionBudget(GH-3397) @jeremydmillerTest infrastructure only
IntegrationContextfrom disposing a class fixture it doesn't own; pinApplicationAssemblyin the CoreTests harness (GH-3423) @jeremydmillerusing_dynamic_multi_tenancyfrom poisoning its own next run @mysticmindMilestone: https://github.com/JasperFx/wolverine/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A6.19.0
Full Changelog: JasperFx/wolverine@V6.18.0...V6.19.0
6.18.0
Wolverine 6.18.0
A security-relevant serialization fix, a startup-fatal codegen fix, a silently-dead-listener fix in RabbitMQ, the first F# saga codegen support of any persistence provider, and the CI split that makes "merge when green" mean something again.
If you use MassTransit interop over a durable listener, take this release. See the first section.
#3408 — fixed in #3411
EnvelopeSerializerwrote the typed envelope properties to the wire format and then appended everyEnvelope.Headersentry verbatim, with no reserved-key filter — and the appended entries came last. Because the reader parses reserved keys straight back into typed properties, aHeadersentry under a reserved key silently overwrote the real property on the next read.A value in
envelope.Headers["tenant-id"]is inert while the envelope is in memory. It stops being inert the moment the envelope crosses the serializer — any durable listener, the inbox/outbox, or the scheduled-message store:tenant-idintoenvelope.Headers.env.TenantIdis set from it.saga-idreaches another saga's state, andidrewritesEnvelope.Id— the inbox's dedupe identity.This was live, not theoretical.
MassTransitEnvelope.TransferDataalready copies every incoming MassTransit header intoenvelope.Headersunfiltered (and by assignment, notTryAdd). Any Wolverine app doing MassTransit interop over a durable listener has had this path open. If that describes you, this release is the one to take.The fix filters reserved keys on the write side, so the typed property stays authoritative and a reserved key sitting in
Headersbecomes a no-op.causation-idis deliberately not filtered —DeliveryOptionsintentionally carries it as a loose header forWolverine.Marten'sOutboxedSessionFactory, and it is never promoted by the reader.Startup-fatal codegen fix
#3399 — fixed in #3406 — invalid generated class name for batched (array) message types. This one prevents the application from starting.
Fixes
DaemonMode.Solo/HotColddaemon alongside managed distribution is now an actionable startup exception instead of two schedulers quietly fighting over the same shards.IAgentRuntime.ApplyRestrictionsAsyncpersisted the restriction and then never dispatched the commands it computed, so pausing an agent had no immediate effect. Reported by @erdtsieck against a live cluster.Internalstatus. It now returns an actionable diagnostic telling you to put the saga identity on the request DTO.[AsParameters]now rejects unparseable values in collection query parameters, closing the gap left by the scalar fix in #3372.IEventStorebridge registered twice, soGetServices<IEventStore>()returned the same store instance two times and anything iterating it double-counted. Polecat's ownAddPolecat()had started registeringIEventStoreand Wolverine was still bridging it as well.State = Connected— a silently dead listener. The listener now defers toReconnectedAsync(), which re-declares and re-consumes. Also pins theConnectionMonitortracking invariant that #3370 fixed but nothing guarded.OpenAPI
#3380 (#3418) — OpenAPI parameters are now derived from the full binding chain rather than the handler signature alone. Two real defects closed:
After/Finallypostprocessor were omitted from the operation entirely.string) whenever the description was assembled before those frames resolved — which is exactly the build-time OpenAPI /openapiCLI path, because ASP.NET caches the first ApiExplorer read.More importantly, this ships the OpenAPI shape-test harness that was missing. Adding a shape assertion is now one endpoint plus one
[Fact], which is why this class of omission kept shipping unnoticed.New: Azure Service Bus emulator support
#3366 (#3409) — the docs told you to call
UseAzureServiceBusTesting(), which only ever existed in Wolverine's own test suite. It is now a real, shipping API:... (truncated)
6.17.3
Bug-fix and scale release, following the 6.17.2 community sweep. Every item below came from a community report or a review finding — thank you all.
Closed issues
PeriodicTimer, so at high database counts the metrics polling itself became significant connection pressure. Agents now register their store with a node-wide sequential sweeper that walks the node's databases one at a time across theUpdateMetricsPeriodwindow — at most one metrics connection in flight per node, regardless of database count. The registration set is re-read every pass, so databases join and leave the sweep as agents start and stop without a restart.Disconnectedstate that #3187 fixed. Two follow-ups are tracked in #3391.TrackedSessionwould pick up and then sit waiting on messages the test never sent. The default ignore rule now covers all ofINotToBeRouted(agent commands and framework telemetry), with a deliberate carve-out forAcknowledgement/FailureAcknowledgement, which the session's own acknowledgement APIs depend on. If you are on an older version,IgnoreMessagesMatchingType(t => t.CanBeCastTo<INotToBeRouted>())is the workaround.Fixes from review
UpdateMetricsPeriod = TimeSpan.Zerowould hot-spin the sweep loop (the pre-#3384PeriodicTimerthrew); it is now rejected at configuration time, withDurabilityMetricsEnabled = falseas the way to turn polling off.Marten test-helper:
PauseThenCatchUpOnMartenDaemonActivity#3388 — cold first catch-up appeared to stall (PR #3394, reported by @uniquelau). Investigated in depth. The reported mechanism — that
coordinator.ResumeAsync()does not start never-started shards — does not hold: under Wolverine-managed distribution the coordinator isWolverineProjectionCoordinator, whoseResumeAsyncbuilds the daemon lazily and starts every shard, bypassing agent assignment entirely. The cold path works, and there are now four tests proving it (including with a second subscription-agent consumer sharing the agent family).The real defect was a timeout mismatch, and it explains the reported symptom exactly. The stage runs inside a child
TrackedSessionwhose token cancels atTrackedSession.Timeout— 5 seconds by default — while the catch-up ignored that token and waited on an internal 60-second budget. The session gave up first and left the catch-up envelope started-but-never-finished, which reads as a hang. This is a genuine 6.16 → 6.17 behavior change: the old activeForceAllfinished inside 5 seconds; resume-and-wait on a cold daemon or a busy machine does not. The catch-up now honors the session's token and raises an actionableTimeoutExceptionnaming the store and pointing atTrackActivity().Timeout(...).If you hit this on 6.17.0–6.17.2, raising the tracked-session timeout is the fix.
Docs
IMessageBus.InvokeAsync<T>and the chain that runs is the handler's. The header-identified gap is tracked as #3385, with a clear diagnostic planned.Timeout()bounds the whole session including its stages, so a slow stage likePauseThenCatchUpOnMartenDaemonActivity()is capped by the session's 5-second default, not by any budget internal to the stage.Full changelog: JasperFx/wolverine@V6.17.2...V6.17.3
Commits viewable in compare view.
Updated WolverineFx.Http from 6.17.2 to 6.23.1.
Release notes
Sourced from WolverineFx.Http's releases.
6.23.1
Agent distribution
TL;DR: if you pause a projection from CritterWatch on 6.23.0, restarting it appears to do nothing for a full minute. This fixes that.
A paused projection or subscription agent now resumes immediately when you restart it (#3663). On 6.23.0 the restart was accepted, the pause restriction was cleared, and then nothing happened until the pending-assignment ledger's TTL expired — 2 ×
CheckAssignmentPeriod, so 60 seconds with the defaults. Long enough that an operator reasonably concludes the agent is never coming back.The ledger introduced in 6.23.0 (#3622) only counted an assignment as confirmed if a later evaluation saw the agent running and still assigned to the same node. A pause makes those two conditions mutually exclusive: the first evaluation that can observe the delivered assignment is the same one that detaches the agent. The entry was never confirmed, nothing on the stop path cleared it, and the restart's
AssignAgentwas suppressed as a duplicate still in flight. Delivery alone now confirms the entry, which is the only question the ledger was ever asking.Pausing an agent no longer briefly starts it first (#3666).
ApplyRestrictionsAsynckickstarted a health check before persisting the operator's restriction change, so that evaluation ran against the old restrictions and could act against the very intent being applied — for a pause, re-assigning and starting the agent one beat before the merged evaluation stopped it again. Besides the wasted daemon start/stop cycle, this is what armed the stale ledger entry behind #3663.PostgreSQL
Advisory-lock sessions stay invisible to Marten's async-daemon gap detection (#3664). Marten 9.16.1+ will not skip a stale event-sequence gap while any session whose open transaction predates that gap is still alive (marten#4953). A session parked in an open transaction for the life of the process therefore reads as a permanent "possible reserver" and can hold the high-water mark — and every async projection — behind a gap that is genuinely dead.
Wolverine's long-held locks were already shaped correctly: leader election and node coordination hold session-scoped advisory locks on a dedicated connection with no transaction, so they show up as
state='idle'with a NULLxact_start. Those sessions are now also taggedapplication_name = 'wolverine-advisory-lock:<database>', which turns apg_stat_activityinvestigation from guesswork into something you can read at a glance. The constraints are pinned in tests and in the Postgres durability docs, including the trap worth knowing in your own code: never add a keepalive query inside a long-lived open transaction — it bumpsstate_change, makes the session look active, and re-promotes it to candidate reserver.Note for combined Marten + Wolverine deployments: older guidance suggested Postgres's
idle_in_transaction_session_timeoutas a dead-gap backstop. Prefer upgrading Marten and usingSkipStaleGapsDespiteLiveTransactionsAfterinstead.6.23.0
Projection & agent distribution
TL;DR: this prevents Wolverine from going into a panic doing agent assignments and churning crazily hard during Kubernetes rollouts or cluster starts and that's a very good thing
The bulk of this release. A wave of fixes (WO-1..8) to the agent assignment plane that together remove the re-assignment churn and livelock that could leave projection agents flapping or wedged.
AssignAgentfloods (#3622)IEventSubscriptionAgent.Failuresurfaces aShardFailure— category, the failing event's sequence and type, and the root exception type — through health checks and a newIWolverineObserver.AgentPausedhook plus aNodeRecordType.AgentPausedrecord. Failures bound to a specific event (ApplyEvent,EventSerialization,UnknownEventType) or to two processes racing one shard (ProgressionOutOfOrder) are no longer auto-restarted, since they would die on the identical event every time (#3637, #3638)Durability.AgentStartRetryAttempts/AgentStartRetryDelay, instead of idling a fullCheckAssignmentPeriodafter a startup race (#3519)FindAgentUriAsyncoverload (#3647) and a store-awareTryRebuildRegisteredProjectionAsyncoverload (#3618)Bumps JasperFx.Events to 2.36.1, Marten to 9.20.0 and Polecat to 5.7.0.
HTTP
Content-Typeon an[AcceptsContentType]route returns 415 rather than 404 (#3649)[AsParameters]endpoints no longer advertise a form body, which had been dropping them from route matching entirely (#3630)PublishMessage<T>andSendMessage<T>— now describe the message they read from the body. They had been advertising no request body at all in OpenAPI (#3646)Transports
ListenToPubsubSubscriptionOnNamedBroker(#3631)EnvelopeMapperreads both timestamp header formats (#3645)EnclosedMessageTypesheader is split before the message type is resolved, so interop works across Azure Service Bus, SNS, SQS and the database transports (#3628)Persistence
DbBatch, so the work splits per statement.Other
Contributors
Thank you to everyone who contributed to this release:
... (truncated)
6.22.0
Wolverine 6.22.0 rolls up the claim-check backend wave, distributed-agent and durability hardening, HTTP/OpenAPI binding fixes, and the Marten 9.18 / JasperFx 2.34 critter-stack alignment.
Dependency alignment
Marten.AspNetCore/Marten.Newtonsoft), Polecat[5.5.0,6.0.0)Claim-check offloading
IClaimCheckStore(#3564)Distributed agents & durability
IHost.ClearAllWolverineStorageAsync()(#3592)EventSubscriptionAgentrestores continuous execution after Rebuild/Rewind (#3520)running_on_node, marten#5001) (#3578)HTTP & OpenAPI
[FromQuery]on arrays/collections instead of misrouting to complex-flattening (#3602)[FromQuery] decimalcorrectly + shape-test OpenAPI parameter description across type families (#3586)[FromQuery]/[FromHeader]parameter twice in OpenAPI (#3586)StreamPaged,StreamPagedByCursor, ETag support (#3593)DbContextparameter as the HTTP request body (#3538)Transports
WolverineFx.Mqtt5package (MQTTnet 5) (#3517)Persistence
RAW(16)Guid correctly (#3581)... (truncated)
6.21.0
Wolverine 6.21.0 is a big one: conjoined multi-tenancy for EF Core, and a measured messaging-performance wave across Kafka and RabbitMQ.
Conjoined multi-tenancy for EF Core (#3465)
Mark an EF Core entity with
ITenanted(the marker shared critter-stack-wide fromJasperFx.MultiTenancy) and register yourDbContextwithAddDbContextWithWolverineManagedConjoinedTenancy<T>(), and Wolverine gives you what Marten users have had for years: a mappedtenant_idcolumn, a tenant-bound global query filter you can't forget, stamp-on-insert,CrossTenantWriteExceptionon cross-tenant writes, conjoined sagas, opt-in Weasel-managed physical tenant partitioning (PostgreSQL list partitions + SQL Server tenant-ordinal), and an authoritativewolverine_tenantsregistry that doubles as a dynamic tenant source and feeds CritterWatch tenant management. The behavior is checked against a port of Marten's conjoined-tenancy compliance battery. See the newConjoinedMultiTenantedEfCoresample app and docs.Messaging performance (GH-3490 / GH-3492)
A client-reported "Wolverine-over-Kafka is 3-12x slower than native" investigation turned into a measured optimization wave (methodology, rigs, and full ledgers are in the repo):
(100, 250ms)settings measured 5.8 seconds publish-to-consume p50; it now measures 136ms, bounded by the timeout. Affects every transport that sends through the batched sender (RabbitMQ routes were unaffected — they don't).MaximumMessagesToReceivelistener knob on both (default 100;1restores strict message-at-a-time persistence).Envelope[]arrival path now applies the same per-envelope guards as single-message arrival (interop serializer unwrap, dead-lettering of unidentifiable messages, expiry, drain latching), and batched inbox writes route to ancillary message stores correctly.SendMessageBatchper-entry failures (throttling, oversize) are now routed to Wolverine's retry machinery instead of being silently dropped — a silent message-loss fix (GH-3493).BufferedInMemory()/ListenerCount()with measured numbers, and a matching page exists for Kafka.Behavior changes to note
Debug(wasInformation) — restore withopts.Policies.MessageSuccessLogLevel(LogLevel.Information).wolverine-execution-timeis now a floating-point histogram and no longer silently drops sub-millisecond executions (same name/unit; the point type changes).Transports and messaging
PrefetchCounton listeners and transport defaults (GH-3471)DelaySecondsfor short scheduled sends on standard queues (GH-3472)DeliverAt(GH-3470)HTTP / gRPC
IAsyncEnumerable<TRequest> -> Task<TResponse>handler shape (#3500)StreamAsync<TRequest, TResponse>overload (#3459)Dependencies
JasperFx 2.30.1 (sender-batching max-age fix), Weasel 9.18.1, Marten 9.16.1.
6.20.0
Wolverine 6.20.0
Dependency upgrades (critter stack)
Multi-tenancy & connection footprint
nodes × databases. Plus daemon tracker-subscription leak hygiene.IntegrateWithWolverine()now honors a database-per-tenant Polecat store and readsMainDatabaseConnectionString.DatabaseDescriptor.Portinstead of re-parsing (jasperfx#514).CritterWatch / connection state
Kafka
SentAtand expose record headers on raw-JSON listeners (GH-3407).JsonSerializerOptionson raw-JSON endpoints now actually applies;PublishRawJsonmapper registration fixed.Sagas
SagaConcurrencyExceptionnow inheritsJasperFx.ConcurrencyException(GH-3444) — existingOnException<ConcurrencyException>()policies now catch saga concurrency failures.Other
TrackedSessionnot-tracked vs not-routed fix (#3435).6.19.0
CosmosDB
CosmosDbConfiguration.PartitionSagasById(): opt-in, saga id becomes the document partition key (GH-3415) @mysticmindCosmosClientwhose serializer would drop a saga'sidat host start; document the camelCase requirement (GH-3416) @mysticmindHTTP / OpenAPI
uuidinstead of falling back tostring(GH-3420) @mysticmindDurability / persistence
IWolverineObserver.ConnectionBudget(GH-3397) @jeremydmillerTest infrastructure only
IntegrationContextfrom disposing a class fixture it doesn't own; pinApplicationAssemblyin the CoreTests harness (GH-3423) @jeremydmillerusing_dynamic_multi_tenancyfrom poisoning its own next run @mysticmindMilestone: https://github.com/JasperFx/wolverine/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A6.19.0
Full Changelog: JasperFx/wolverine@V6.18.0...V6.19.0
6.18.0
Wolverine 6.18.0
A security-relevant serialization fix, a startup-fatal codegen fix, a silently-dead-listener fix in RabbitMQ, the first F# saga codegen support of any persistence provider, and the CI split that makes "merge when green" mean something again.
If you use MassTransit interop over a durable listener, take this release. See the first section.
#3408 — fixed in #3411
EnvelopeSerializerwrote the typed envelope properties to the wire format and then appended everyEnvelope.Headersentry verbatim, with no reserved-key filter — and the appended entries came last. Because the reader parses reserved keys straight back into typed properties, aHeadersentry under a reserved key silently overwrote the real property on the next read.A value in
envelope.Headers["tenant-id"]is inert while the envelope is in memory. It stops being inert the moment the envelope crosses the serializer — any durable listener, the inbox/outbox, or the scheduled-message store:tenant-idintoenvelope.Headers.env.TenantIdis set from it.saga-idreaches another saga's state, andidrewritesEnvelope.Id— the inbox's dedupe identity.This was live, not theoretical.
MassTransitEnvelope.TransferDataalready copies every incoming MassTransit header intoenvelope.Headersunfiltered (and by assignment, notTryAdd). Any Wolverine app doing MassTransit interop over a durable listener has had this path open. If that describes you, this release is the one to take.The fix filters reserved keys on the write side, so the typed property stays authoritative and a reserved key sitting in
Headersbecomes a no-op.causation-idis deliberately not filtered —DeliveryOptionsintentionally carries it as a loose header forWolverine.Marten'sOutboxedSessionFactory, and it is never promoted by the reader.Startup-fatal codegen fix
#3399 — fixed in #3406 — invalid generated class name for batched (array) message types. This one prevents the application from starting.
Fixes
DaemonMode.Solo/HotColddaemon alongside managed distribution is now an actionable startup exception instead of two schedulers quietly fighting over the same shards.IAgentRuntime.ApplyRestrictionsAsyncpersisted the restriction and then never dispatched the commands it computed, so pausing an agent had no immediate effect. Reported by @erdtsieck against a live cluster.Internalstatus. It now returns an actionable diagnostic telling you to put the saga identity on the request DTO.[AsParameters]now rejects unparseable values in collection query parameters, closing the gap left by the scalar fix in #3372.IEventStorebridge registered twice, soGetServices<IEventStore>()returned the same store instance two times and anything iterating it double-counted. Polecat's ownAddPolecat()had started registeringIEventStoreand Wolverine was still bridging it as well.State = Connected— a silently dead listener. The listener now defers toReconnectedAsync(), which re-declares and re-consumes. Also pins theConnectionMonitortracking invariant that #3370 fixed but nothing guarded.OpenAPI
#3380 (#3418) — OpenAPI parameters are now derived from the full binding chain rather than the handler signature alone. Two real defects closed:
After/Finallypostprocessor were omitted from the operation entirely.string) whenever the description was assembled before those frames resolved — which is exactly the build-time OpenAPI /openapiCLI path, because ASP.NET caches the first ApiExplorer read.More importantly, this ships the OpenAPI shape-test harness that was missing. Adding a shape assertion is now one endpoint plus one
[Fact], which is why this class of omission kept shipping unnoticed.New: Azure Service Bus emulator support
#3366 (#3409) — the docs told you to call
UseAzureServiceBusTesting(), which only ever existed in Wolverine's own test suite. It is now a real, shipping API:... (truncated)
6.17.3
Bug-fix and scale release, following the 6.17.2 community sweep. Every item below came from a community report or a review finding — thank you all.
Closed issues
PeriodicTimer, so at high database counts the metrics polling itself became significant connection pressure. Agents now register their store with a node-wide sequential sweeper that walks the node's databases one at a time across theUpdateMetricsPeriodwindow — at most one metrics connection in flight per node, regardless of database count. The registration set is re-read every pass, so databases join and leave the sweep as agents start and stop without a restart.Disconnectedstate that #3187 fixed. Two follow-ups are tracked in #3391.TrackedSessionwould pick up and then sit waiting on messages the test never sent. The default ignore rule now covers all ofINotToBeRouted(agent commands and framework telemetry), with a deliberate carve-out forAcknowledgement/FailureAcknowledgement, which the session's own acknowledgement APIs depend on. If you are on an older version,IgnoreMessagesMatchingType(t => t.CanBeCastTo<INotToBeRouted>())is the workaround.Fixes from review
UpdateMetricsPeriod = TimeSpan.Zerowould hot-spin the sweep loop (the pre-#3384PeriodicTimerthrew); it is now rejected at configuration time, withDurabilityMetricsEnabled = falseas the way to turn polling off.Marten test-helper:
PauseThenCatchUpOnMartenDaemonActivity#3388 — cold first catch-up appeared to stall (PR #3394, reported by @uniquelau). Investigated in depth. The reported mechanism — that
coordinator.ResumeAsync()does not start never-started shards — does not hold: under Wolverine-managed distribution the coordinator isWolverineProjectionCoordinator, whoseResumeAsyncbuilds the daemon lazily and starts every shard, bypassing agent assignment entirely. The cold path works, and there are now four tests proving it (including with a second subscription-agent consumer sharing the agent family).The real defect was a timeout mismatch, and it explains the reported symptom exactly. The stage runs inside a child
TrackedSessionwhose token cancels atTrackedSession.Timeout— 5 seconds by default — while the catch-up ignored that token and waited on an internal 60-second budget. The session gave up first and left the catch-up envelope started-but-never-finished, which reads as a hang. This is a genuine 6.16 → 6.17 behavior change: the old activeForceAllfinished inside 5 seconds; resume-and-wait on a cold daemon or a busy machine does not. The catch-up now honors the session's token and raises an actionableTimeoutExceptionnaming the store and pointing atTrackActivity().Timeout(...).If you hit this on 6.17.0–6.17.2, raising the tracked-session timeout is the fix.
Docs
IMessageBus.InvokeAsync<T>and the chain that runs is the handler's. The header-identified gap is tracked as #3385, with a clear diagnostic planned.Timeout()bounds the whole session including its stages, so a slow stage likePauseThenCatchUpOnMartenDaemonActivity()is capped by the session's 5-second default, not by any budget internal to the stage.Full changelog: JasperFx/wolverine@V6.17.2...V6.17.3
Commits viewable in compare view.
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)