Repository navigation
GH-5160, GH-5165, GH-5166: [DeduplicatedWithResponse] ClaimInTransaction, plus two Marten commit-failure fixes - #5169
Conversation
… the claim and its response inside the endpoint's transaction An opt-in ClaimInTransaction on [DeduplicatedWithResponse]. On an endpoint that commits through a Marten session with Wolverine's transactional middleware, on a single-database store with the PostgreSQL message store in the same database, the claim and its stored response are written as one row, queued onto the session before SaveChangesAsync. A rollback, or a crash before the commit, leaves no claim; a commit leaves it answered. There is no release and no record left to fail afterwards. The default is unchanged. - Before the endpoint runs, the chain answers from any committed claim. - A commit that fails is answered from a claim a concurrent request committed meanwhile, on the claim's primary key or on the endpoint's own work (a stream version conflict); otherwise it is rethrown. A failure after this request's own database commit (the outbox flush runs inside SaveChangesAsync on Marten) is told apart by the claim token and rethrown. - An endpoint that asks for it but cannot take it keeps claim-and-release and logs a warning, including one whose response is produced after the commit (UpdatedAggregate). The same-database check is repeated at runtime for pre-generated code. - The provider seam (ITransactionalReplayableDeduplicationProvider) and the ownership query are internal; no existing interface changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxXDGaqdYTzZ54ZmdzUwH4
…lure after the database commit From the Copilot review on #5161, both reproduced before fixing. - MartenDeduplicatedResponses cached the claims table by the session's NpgsqlDataSource alone. An application that hands one data source to its main and ancillary stores (StoreOptions.Connection(dataSource)) got the first store's table for both: the ancillary claim landed where it is never read, and the repeat ran the endpoint again and failed at commit. The cache is now keyed by data source and store marker. - A Marten session listener that throws from AfterCommitAsync fails SaveChangesAsync after the database commit. The exception now has a test: it reaches the caller, the answered claim and the work stay committed once, and the retry replays without running the endpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxXDGaqdYTzZ54ZmdzUwH4
…e, as [Deduplicated] does (GH-4505) Aligns the in-transaction path with [Deduplicated] on Marten. - The existing-claim lookup now goes through the Marten session, supplied by the provider as TransactionalDeduplication.Check is. It is a query handler returning the stored claim, and it joins the chain's batched query when the key is required, so a [WriteAggregate] endpoint pays one round trip for the lookup and FetchForWriting together. An optional key stays standalone, so a request without one costs no round trip. - The runtime seam is now IMartenDeduplicatedResponses, a public interface with an internal implementation, as IMartenDeduplicator is. - HTTP computes the hashed storage id and answers from the provider's lookup; the separate-connection lookup frame is gone. The race handling deliberately stays different. A probe of [Deduplicated] showed that its concurrent [WriteAggregate] loser fails on the stream version before reaching the claim, and gets a 500 rather than the 409. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxXDGaqdYTzZ54ZmdzUwH4
…e SaveChangesAsync can still throw Marten runs its session listeners' AfterCommitAsync inside SaveChangesAsync, after the database commit, Wolverine's outbox flush among them. From outside the call a failure there looked the same as a failed commit. A DatabaseCommitMarker listener, inserted at the front of the session's listeners, now records when the commit started and when the database commit succeeded. GH-5165: on a claim-and-release [DeduplicatedWithResponse] Marten endpoint, a failure after the database commit released the claim, so the client's retry ran the committed work again. The chain now hears the commit (internal IDatabaseCommitHookProvider seam, Marten only) and keeps the claim unanswered on a failure over committed work: repeats get 409 until it expires, the same as a failed record. A request that completes normally keeps the existing rule. GH-5166: on [Deduplicated] with Marten (GH-4505), a concurrent [WriteAggregate] loser fails on the stream version before reaching the claim's INSERT, so the unique-violation classifier never matched and it escaped as a 500. A commit that fails for any other reason is now checked on a fresh query session: if the key is claimed, it gets the same refusal (409 / discard), otherwise the failure is rethrown unaltered. Failures before the commit or after the database commit are never looked up. Co-Authored-By: Laurence Gillian <lau@eddlondon.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
|
Thanks for picking these up, and for keeping the #5160 commits as they were. Two things for #5169, both with a follow-up I can send once it has merged. 1. On the The response is serialized just before
The #5161 description lists this as a trade-off, but I don't think it's one worth keeping: a client using that version for its next write would be handed a stale one on every retry. A probe of Marten's operation lifecycle showed the Guid version is set before the batch executes, but the numeric revision only after its command has run. So no single write inside the transaction can carry both. The fix I'd send keeps the claim in the transaction and records the response again after the commit, as claim-and-release does today. It overwrites the stored response only when the commit changed it, using a token-guarded internal store call, so no public interface changes. 2. For #5165, an alternative that replays instead of refusing. With I have a local branch that does it differently. On a Marten chain it queues the response record onto the session, the way The 409 is a sound answer, so I'm only raising the replay as an option. If you'd like it, I'll rebase it onto #5169 as a follow-up. |
|
A review of 1. #5166 under
The default is fine. Rich mode is a common opt-in, though, and 2. Marten runs Wolverine's own message batch before the listener loop. Ordering confirmed in Marten 9.51's source; not run.
Suggested fix: record "committed" from the earliest post-COMMIT hook Marten exposes, if there is a public one; otherwise note the gap. 3. A user's 4. 5. The marker counts every commit in the request, not just the middleware's. Plausible; not run. |
Updated [Aspire.Hosting.AppHost](https://github.com/microsoft/aspire)
from 13.6.3 to 13.6.4.
<details>
<summary>Release notes</summary>
_Sourced from [Aspire.Hosting.AppHost's
releases](https://github.com/microsoft/aspire/releases)._
## 13.6.4
## What's New in Aspire 13.6.4
Patch release for Aspire 13.6 that improves resource startup
reliability, fixes Azure publishing with parameterized enum properties
in TypeScript AppHosts, and tightens CLI diagnostic log permissions on
Unix.
### 🐛 Fixes
- 🖥️ **Resources could fail to start after a DCP create request timed
out** — AppHost now checks whether a resource was created before
retrying a timed-out or HTTP 504 request, avoiding a premature
`FailedToStart` state when DCP completed the operation. API operations
default to a 40-second timeout with an additional 20-second allowance
before the first successful operation, and resource creation has a
two-minute recovery budget. These limits can be configured through
`DcpPublisher:KubernetesApiTimeout`,
`DcpPublisher:KubernetesInitializationAdditionalTimeout`, and
`DcpPublisher:KubernetesCreateRecoveryTimeout`. Established watch and
log streams are not limited by these operation timeouts. Addresses
startup timeouts reported in 13.6. Fixes
[#20691](https://github.com/microsoft/aspire/issues/20691).
([#20868](https://github.com/microsoft/aspire/pull/20868), backport of
[#20752](https://github.com/microsoft/aspire/pull/20752), @danegsta)
- ☁️ **TypeScript AppHosts could fail to publish parameterized Azure
enum properties** — Assigning a `bicep().parameter(...)` expression to
an enum-valued provisioning property, such as an Azure Storage SKU name,
now works instead of failing with a type-assignment error. Type checking
for literal values and incompatible expression types is preserved. Fixes
a scenario introduced with the Azure provisioning proxy feature in 13.6.
([#20767](https://github.com/microsoft/aspire/pull/20767), backport of
[#20557](https://github.com/microsoft/aspire/pull/20557),
@sebastienros)
- 🔒 **CLI diagnostic logs now use owner-only permissions on Unix** — Log
files are restricted to owner read/write access (`0600`), including
reused files, and the dedicated Aspire logs directory uses owner-only
access (`0700`), repairing existing permissions when needed. Newly
created custom log directories also use `0700`; existing custom parent
directories and Windows permission behavior are unchanged. Diagnostic
logs can contain sensitive data, including dashboard login URLs, so
review them before sharing.
([#21282](https://github.com/microsoft/aspire/pull/21282), backport of
[#21276](https://github.com/microsoft/aspire/pull/21276), @mitchdenny)
---
*Full Changelog:
[v13.6.3...v13.6.4](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4)*
*Full commit:
[c89352703f78ae46de90fbf751ed8b52b77b89bf](https://github.com/microsoft/aspire/commit/c89352703f78ae46de90fbf751ed8b52b77b89bf)*
Commits viewable in [compare
view](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4).
</details>
Updated [Aspire.Hosting.PostgreSQL](https://github.com/microsoft/aspire)
from 13.6.3 to 13.6.4.
<details>
<summary>Release notes</summary>
_Sourced from [Aspire.Hosting.PostgreSQL's
releases](https://github.com/microsoft/aspire/releases)._
## 13.6.4
## What's New in Aspire 13.6.4
Patch release for Aspire 13.6 that improves resource startup
reliability, fixes Azure publishing with parameterized enum properties
in TypeScript AppHosts, and tightens CLI diagnostic log permissions on
Unix.
### 🐛 Fixes
- 🖥️ **Resources could fail to start after a DCP create request timed
out** — AppHost now checks whether a resource was created before
retrying a timed-out or HTTP 504 request, avoiding a premature
`FailedToStart` state when DCP completed the operation. API operations
default to a 40-second timeout with an additional 20-second allowance
before the first successful operation, and resource creation has a
two-minute recovery budget. These limits can be configured through
`DcpPublisher:KubernetesApiTimeout`,
`DcpPublisher:KubernetesInitializationAdditionalTimeout`, and
`DcpPublisher:KubernetesCreateRecoveryTimeout`. Established watch and
log streams are not limited by these operation timeouts. Addresses
startup timeouts reported in 13.6. Fixes
[#20691](https://github.com/microsoft/aspire/issues/20691).
([#20868](https://github.com/microsoft/aspire/pull/20868), backport of
[#20752](https://github.com/microsoft/aspire/pull/20752), @danegsta)
- ☁️ **TypeScript AppHosts could fail to publish parameterized Azure
enum properties** — Assigning a `bicep().parameter(...)` expression to
an enum-valued provisioning property, such as an Azure Storage SKU name,
now works instead of failing with a type-assignment error. Type checking
for literal values and incompatible expression types is preserved. Fixes
a scenario introduced with the Azure provisioning proxy feature in 13.6.
([#20767](https://github.com/microsoft/aspire/pull/20767), backport of
[#20557](https://github.com/microsoft/aspire/pull/20557),
@sebastienros)
- 🔒 **CLI diagnostic logs now use owner-only permissions on Unix** — Log
files are restricted to owner read/write access (`0600`), including
reused files, and the dedicated Aspire logs directory uses owner-only
access (`0700`), repairing existing permissions when needed. Newly
created custom log directories also use `0700`; existing custom parent
directories and Windows permission behavior are unchanged. Diagnostic
logs can contain sensitive data, including dashboard login URLs, so
review them before sharing.
([#21282](https://github.com/microsoft/aspire/pull/21282), backport of
[#21276](https://github.com/microsoft/aspire/pull/21276), @mitchdenny)
---
*Full Changelog:
[v13.6.3...v13.6.4](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4)*
*Full commit:
[c89352703f78ae46de90fbf751ed8b52b77b89bf](https://github.com/microsoft/aspire/commit/c89352703f78ae46de90fbf751ed8b52b77b89bf)*
Commits viewable in [compare
view](https://github.com/microsoft/aspire/compare/v13.6.3...v13.6.4).
</details>
Updated
[Microsoft.AspNetCore.Authentication.JwtBearer](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.AspNetCore.Authentication.JwtBearer's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.AspNetCore.Mvc.Testing's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated [Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.AspNetCore.OpenApi's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated [Microsoft.Bcl.TimeProvider](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Bcl.TimeProvider's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore](https://github.com/dotnet/dotnet) from
10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore.Abstractions](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore.Abstractions's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore.Design](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore.Design's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.EntityFrameworkCore.Tools's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.ApiDescription.Server](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.ApiDescription.Server's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Caching.Memory](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Caching.Memory's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Configuration.EnvironmentVariables](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Configuration.EnvironmentVariables's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Configuration.Json](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Configuration.Json's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.DependencyInjection.Abstractions](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.DependencyInjection.Abstractions's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from
[Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Extensions.Hosting.Abstractions](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Hosting.Abstractions's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated [Microsoft.Extensions.Http](https://github.com/dotnet/dotnet)
from 10.0.13 to 10.0.14.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Extensions.Http's
releases](https://github.com/dotnet/dotnet/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>
Updated
[Microsoft.Testing.Extensions.TrxReport](https://github.com/microsoft/testfx)
from 2.5.1 to 2.5.2.
<details>
<summary>Release notes</summary>
_Sourced from [Microsoft.Testing.Extensions.TrxReport's
releases](https://github.com/microsoft/testfx/releases)._
No release notes found for this version range.
Commits viewable in [compare
view](https://github.com/microsoft/testfx/commits).
</details>
Updated [WolverineFx](http://github.com/jasperfx/wolverine) from 6.51.3
to 6.56.0.
<details>
<summary>Release notes</summary>
_Sourced from [WolverineFx's
releases](http://github.com/jasperfx/wolverine/releases)._
## 6.56.0
## New
- **GH-5238** (#5242): `opts.UseHttpTransport()` registers the HTTP
transport inside `UseWolverine()`. `http`/`https` URIs then resolve in
`ListenForMessagesFrom()`, `PublishMessage<T>().To(uri)`, and runtime
`EndpointFor()` / `ToDestination()`. Before this, the transport was only
registered by `ToHttpEndpoint()`, and any other `http` URI failed with
`Unknown Transport scheme 'http'`. `/_wolverine/invoke` now answers 415
instead of 500 for a content type with no registered serializer.
## Fixes
- **GH-5235** (#5239): Host shutdown now stops the durability metrics
sweeper and waits for any query it has in flight before stores are
disposed. Removing a durability agent also waits for its in-flight
query. A metrics query could previously outlive `IHost.StopAsync()` and
throw `ObjectDisposedException` against a disposed `NpgsqlDataSource`.
- **GH-5236** (#5240): A Kafka listener with a durable inbox no longer
crashes the process with a stack overflow when the inbox database is
unavailable. `DeferAsync()` used to call back into the receiver inline,
so the failed inbox write and the defer recursed. A deferred Kafka
envelope now goes back to the receiver on its own task after a short
pause, and only while the listener is running. After a stop or
inbox-recovery pause, its uncommitted offset brings it back. Because
Kafka has no native requeue, a `Requeue` error policy on a Kafka
endpoint now redelivers after about one second instead of immediately.
- **GH-5237** (#5241): A CloudEvents HTTP endpoint now fails a
single-envelope send that the receiver rejects. It used to count the
send as delivered, so a durable endpoint aimed at `/_wolverine/invoke`
could delete an outgoing envelope that was never received. HTTP
transport responses are now disposed.
## 6.55.1
## Fixes
- **GH-5230** (#5232): A database operation batch cancelled by
Wolverine's own shutdown is no longer wrapped in
`DatabaseBatchCommandException` and logged as an Error. Genuine
SQL/connection failures are still reported.
- **GH-5231** (#5233): An `HttpEndpoint` aimed at `/_wolverine/invoke`
in its default (buffered) mode now delivers by posting each envelope
individually, instead of receiving a 415 for every batch and retrying
forever. `/batch/{queue}` and `.SendInline()` endpoints are unchanged.
## 6.55.0
## Highlights
**WolverineFx.Bobcat is out of alpha, on Bobcat 1.0.0** (#5222,
#5229). Bobcat 1.0.0 shipped tonight, and `WolverineFx.Bobcat` and
`WolverineFx.Bobcat.EventModel` now publish as stable **6.55.0** instead
of `-alpha.1`. `WolverineFx.Bobcat.Http` stays `6.55.0-alpha.1` until
Alba 9 ships stable, because a stable package cannot depend on Alba
9.0.0-rc.2. A spec project that `write-specs` generates with no
`--wolverine-version` pins the stable `WolverineFx.Bobcat` (#5222,
#5223).
**`MessageCausedBy` names your handler** (#5224, #5225).
`IWolverineObserver.MessageCausedBy` reported the generated handler
class
(`Internal.Generated.WolverineHandlers.StartTripHandler939323516`). It
now reports the handler you wrote (`TripService.StartTripHandler`), the
same string as the `message.handler` span tag, resolved at codegen time.
Code generated by an earlier version keeps working through the old
overload.
**CosmosDB sagas start with a conditional create** (#5221, thanks
@thechucklingatom). A second start message for an existing saga id used
to silently overwrite the running saga. It now fails with the
`SagaConcurrencyException` that Marten, Azure Blob and S3 already raise,
so one `OnException<SagaConcurrencyException>()` policy covers every
provider.
## Fixes
* GH-5217: `ListeningAgent.StartAsync` is serialized, and deliveries are
deferred to a drained `BufferedReceiver` (#5228)
* GH-5218: an unresolvable tenant is treated as tenant-scoped on the
single-envelope inbox write (#5227)
* GH-5220: Azure Service Bus listeners join their receive loop before
disposing the `CancellationTokenSource` (#5226)
* GH-5215: reserved-word schema names are quoted in the remaining RDBMS
and PostgreSQL SQL (#5216, thanks @marc-RocketRez)
* GH-5213: a later pause supersedes an earlier pause's restart (#5214)
* GH-5209: the dead letter history table round-trips with no schema
drift (#5212)
## Dependencies
* JasperFx 2.96.0 (jasperfx#1083: an Observed vs Derived `Origin` no
longer raises a `SourceDisagreement`)
* Weasel 9.47.0 (#5219)
* Bobcat 1.0.0
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.54.0...V6.55.0
## 6.54.0
## Highlights
**Dead letter failure groups** (#5209, #5210). Wolverine now
fingerprints every message as it moves to dead letter storage, so dead
letters can be managed one failure group at a time:
* The default `IDeadLetterFingerprinter` hashes the exception type and
the first stack frame from application code. Register your own to
replace it.
* `DeadLetterEnvelopeQuery` gains `FailureGroup`,
`ExceptionMessageMatch` (Exact / Contains), `TenantId` and `Archived`.
* A new `SummarizeAllAsync` overload groups by failure group, message
type or endpoint, with first and last failure times.
* `ReplayMatchingAsync`, `DiscardMatchingAsync`, `ArchiveMatchingAsync`
and `RestoreMatchingAsync` report how many dead letters they affected.
* A message that fails again after a replay reports its `ReplayCount`,
`LastReplayedAt` and `FirstFailedAt`.
* Archived dead letters drop out of summaries, queries and replays until
restored, and can be expired separately with
`DurabilitySettings.ArchivedDeadLetterRetention`.
* `IDeadLetters.Capabilities` tells you what a store supports.
All changes are additive. The relational stores pick up seven nullable
columns on `wolverine_dead_letters`, an index and a new
`wolverine_dead_letter_history` table through the normal schema
migration. See the [dead letter storage
guide](https://wolverinefx.net/guide/durability/dead-letter-storage.html).
**Deduplication fixes** (#5205, #5206, #5208), and **Bobcat
1.0.0-rc.4** dependency pins (#5211). The `WolverineFx.Bobcat*`
packages remain alpha (`6.54.0-alpha.1`).
## What's Changed
* GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps
([WriteAggregate] T / [BoundaryModel], and repeats refused over
committed work) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5208
* Bobcat 1.0.0-rc.4 by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5211
* GH-5209 Dead letters: failure fingerprints, operation counts, replay
history, archive/restore and tenant scoping by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5210
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.53.0...V6.54.0
## 6.53.0
## What's Changed
* GH-5202: keep the ASB response queue identity stable during startup by
@DeandreT in https://github.com/JasperFx/wolverine/pull/5203
## New Contributors
* @DeandreT made their first contribution in
https://github.com/JasperFx/wolverine/pull/5203
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.52.0...V6.53.0
## 6.52.0
Wolverine 6.52.0 is a feature release. The headline items are
Aspire-style registration by connection-string name for every transport
and every message store, timer agents for short-cadence recurring
schedules, and the 6.x groundwork for Wolverine 7.0's
one-chain-per-handler model. There are no breaking API changes. Two
behavior changes are worth knowing about up front: recurring schedules
that fire more often than every 15 minutes now run on a timer agent (opt
out per schedule with `UseScheduledMessages()`), and outgoing envelopes
a transport cannot map are dead-lettered instead of being logged and
deleted.
Many thanks to everyone who reported issues and sent fixes: @uniquelau,
@johnkaram02, @erdtsieck, @ahmetvayhan and @pritamrao.
## Aspire parity: named connections
* Named-connection overloads for every transport (Aspire parity,
GH-5126) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5159
* Message storage by connection-string name for every RDBMS provider
(PostgreSQL, SQL Server, MySQL, SQLite, Oracle), `NpgsqlDataSource` /
`MySqlDataSource` from the container, named static tenants, a Named
Connection Strings table in `describe`, and docs that lead with the
named form (GH-5127, GH-5156) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5174
* Aspire samples moved to Aspire 13.6.3, added to `wolverine.slnx`, with
a ServiceDefaults project for traces, metrics and `/health` (GH-5124) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5182
## Messaging and error handling
* Timer agents for short-cadence recurring schedules: one agent per
schedule under `opts.Schedules.TimerThreshold` (15 minutes), same dedup
id as the scheduled path, pause and resume through
`IRecurringScheduleControl` (GH-5168) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5183
* `DoThenRetry(action, maxAttempts)` error policy that works both inline
and from a queue (GH-5068) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5179
* Unmappable outgoing envelopes are dead-lettered on Azure Service Bus,
SNS and Kafka instead of being reported as sent (GH-5055) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5185
* Durable outbox coalesces stores and deletes per tenant database, so
one slow tenant no longer holds back every tenant's sends (GH-5171) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5180
* 6.x prep for HandlerChain 1:1 in 7.0:
`MultipleHandlerBehavior.ClassicCombineIntoOneLogicalHandler` is
`[Obsolete]` (still the default), a startup warning for combined
handlers, opt-in `AssertNoCombinedHandlers()`, fixes for latent
`Separated` bugs, a `handler.type` OpenTelemetry tag on every chain, and
logical deduplication scoped per chain (GH-5133) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5184
## HTTP
* `[DeduplicatedWithResponse]` `ClaimInTransaction`, plus two Marten
commit-failure fixes (GH-5160, GH-5165, GH-5166) by @jeremydmiller with
@uniquelau in https://github.com/JasperFx/wolverine/pull/5169
* `WolverineHttpOptions.DefaultClaimInTransaction`, an application-wide
default (GH-5160) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5173
* A string response declares its UTF-8 byte count as `Content-Length`
(GH-5187) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5188
* Bind array and list query parameters whose `[FromQuery]` name is not a
C# identifier (GH-5189) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5190
## Operability and diagnostics
* `ServiceCapabilities` reads each store on its own and reports
`FailedSections`, so a failed section is distinguishable from an empty
one (GH-5162) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5175
* `AssignmentGrid.Agent.UnplacedReason` (`AwaitingCapacity`,
`NoCapableNode`, `Paused`, `TakeoverHold`, `Superseded`), a capacity log
line and an optional `NodeAcceptThreshold` (GH-5163) by @jeremydmiller
in https://github.com/JasperFx/wolverine/pull/5176 and
https://github.com/JasperFx/wolverine/pull/5186
* Header-equality filter on `DeadLetterEnvelopeQuery`, honored by bulk
replay and discard, across every dead letter store (GH-5164) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5181
* `db-list` / `db-dump` / `db-patch` discover the Oracle message store
(GH-5172) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5177
## Documentation
* The remaining serverless and Cosmos DB documentation (GH-5054) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5178
* Memory sizing for managed event subscription distribution at tenants x
projections scale: set `MaximumHopperSize` to about 2 x `BatchSize`
(GH-5170, follow-up JasperFx/jasperfx#1076) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5199
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.51.3...V6.52.0
Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.51.3...V6.56.0).
</details>
Updated
[WolverineFx.FluentValidation](http://github.com/jasperfx/wolverine)
from 6.51.3 to 6.56.0.
<details>
<summary>Release notes</summary>
_Sourced from [WolverineFx.FluentValidation's
releases](http://github.com/jasperfx/wolverine/releases)._
## 6.56.0
## New
- **GH-5238** (#5242): `opts.UseHttpTransport()` registers the HTTP
transport inside `UseWolverine()`. `http`/`https` URIs then resolve in
`ListenForMessagesFrom()`, `PublishMessage<T>().To(uri)`, and runtime
`EndpointFor()` / `ToDestination()`. Before this, the transport was only
registered by `ToHttpEndpoint()`, and any other `http` URI failed with
`Unknown Transport scheme 'http'`. `/_wolverine/invoke` now answers 415
instead of 500 for a content type with no registered serializer.
## Fixes
- **GH-5235** (#5239): Host shutdown now stops the durability metrics
sweeper and waits for any query it has in flight before stores are
disposed. Removing a durability agent also waits for its in-flight
query. A metrics query could previously outlive `IHost.StopAsync()` and
throw `ObjectDisposedException` against a disposed `NpgsqlDataSource`.
- **GH-5236** (#5240): A Kafka listener with a durable inbox no longer
crashes the process with a stack overflow when the inbox database is
unavailable. `DeferAsync()` used to call back into the receiver inline,
so the failed inbox write and the defer recursed. A deferred Kafka
envelope now goes back to the receiver on its own task after a short
pause, and only while the listener is running. After a stop or
inbox-recovery pause, its uncommitted offset brings it back. Because
Kafka has no native requeue, a `Requeue` error policy on a Kafka
endpoint now redelivers after about one second instead of immediately.
- **GH-5237** (#5241): A CloudEvents HTTP endpoint now fails a
single-envelope send that the receiver rejects. It used to count the
send as delivered, so a durable endpoint aimed at `/_wolverine/invoke`
could delete an outgoing envelope that was never received. HTTP
transport responses are now disposed.
## 6.55.1
## Fixes
- **GH-5230** (#5232): A database operation batch cancelled by
Wolverine's own shutdown is no longer wrapped in
`DatabaseBatchCommandException` and logged as an Error. Genuine
SQL/connection failures are still reported.
- **GH-5231** (#5233): An `HttpEndpoint` aimed at `/_wolverine/invoke`
in its default (buffered) mode now delivers by posting each envelope
individually, instead of receiving a 415 for every batch and retrying
forever. `/batch/{queue}` and `.SendInline()` endpoints are unchanged.
## 6.55.0
## Highlights
**WolverineFx.Bobcat is out of alpha, on Bobcat 1.0.0** (#5222,
#5229). Bobcat 1.0.0 shipped tonight, and `WolverineFx.Bobcat` and
`WolverineFx.Bobcat.EventModel` now publish as stable **6.55.0** instead
of `-alpha.1`. `WolverineFx.Bobcat.Http` stays `6.55.0-alpha.1` until
Alba 9 ships stable, because a stable package cannot depend on Alba
9.0.0-rc.2. A spec project that `write-specs` generates with no
`--wolverine-version` pins the stable `WolverineFx.Bobcat` (#5222,
#5223).
**`MessageCausedBy` names your handler** (#5224, #5225).
`IWolverineObserver.MessageCausedBy` reported the generated handler
class
(`Internal.Generated.WolverineHandlers.StartTripHandler939323516`). It
now reports the handler you wrote (`TripService.StartTripHandler`), the
same string as the `message.handler` span tag, resolved at codegen time.
Code generated by an earlier version keeps working through the old
overload.
**CosmosDB sagas start with a conditional create** (#5221, thanks
@thechucklingatom). A second start message for an existing saga id used
to silently overwrite the running saga. It now fails with the
`SagaConcurrencyException` that Marten, Azure Blob and S3 already raise,
so one `OnException<SagaConcurrencyException>()` policy covers every
provider.
## Fixes
* GH-5217: `ListeningAgent.StartAsync` is serialized, and deliveries are
deferred to a drained `BufferedReceiver` (#5228)
* GH-5218: an unresolvable tenant is treated as tenant-scoped on the
single-envelope inbox write (#5227)
* GH-5220: Azure Service Bus listeners join their receive loop before
disposing the `CancellationTokenSource` (#5226)
* GH-5215: reserved-word schema names are quoted in the remaining RDBMS
and PostgreSQL SQL (#5216, thanks @marc-RocketRez)
* GH-5213: a later pause supersedes an earlier pause's restart (#5214)
* GH-5209: the dead letter history table round-trips with no schema
drift (#5212)
## Dependencies
* JasperFx 2.96.0 (jasperfx#1083: an Observed vs Derived `Origin` no
longer raises a `SourceDisagreement`)
* Weasel 9.47.0 (#5219)
* Bobcat 1.0.0
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.54.0...V6.55.0
## 6.54.0
## Highlights
**Dead letter failure groups** (#5209, #5210). Wolverine now
fingerprints every message as it moves to dead letter storage, so dead
letters can be managed one failure group at a time:
* The default `IDeadLetterFingerprinter` hashes the exception type and
the first stack frame from application code. Register your own to
replace it.
* `DeadLetterEnvelopeQuery` gains `FailureGroup`,
`ExceptionMessageMatch` (Exact / Contains), `TenantId` and `Archived`.
* A new `SummarizeAllAsync` overload groups by failure group, message
type or endpoint, with first and last failure times.
* `ReplayMatchingAsync`, `DiscardMatchingAsync`, `ArchiveMatchingAsync`
and `RestoreMatchingAsync` report how many dead letters they affected.
* A message that fails again after a replay reports its `ReplayCount`,
`LastReplayedAt` and `FirstFailedAt`.
* Archived dead letters drop out of summaries, queries and replays until
restored, and can be expired separately with
`DurabilitySettings.ArchivedDeadLetterRetention`.
* `IDeadLetters.Capabilities` tells you what a store supports.
All changes are additive. The relational stores pick up seven nullable
columns on `wolverine_dead_letters`, an index and a new
`wolverine_dead_letter_history` table through the normal schema
migration. See the [dead letter storage
guide](https://wolverinefx.net/guide/durability/dead-letter-storage.html).
**Deduplication fixes** (#5205, #5206, #5208), and **Bobcat
1.0.0-rc.4** dependency pins (#5211). The `WolverineFx.Bobcat*`
packages remain alpha (`6.54.0-alpha.1`).
## What's Changed
* GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps
([WriteAggregate] T / [BoundaryModel], and repeats refused over
committed work) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5208
* Bobcat 1.0.0-rc.4 by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5211
* GH-5209 Dead letters: failure fingerprints, operation counts, replay
history, archive/restore and tenant scoping by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5210
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.53.0...V6.54.0
## 6.53.0
## What's Changed
* GH-5202: keep the ASB response queue identity stable during startup by
@DeandreT in https://github.com/JasperFx/wolverine/pull/5203
## New Contributors
* @DeandreT made their first contribution in
https://github.com/JasperFx/wolverine/pull/5203
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.52.0...V6.53.0
## 6.52.0
Wolverine 6.52.0 is a feature release. The headline items are
Aspire-style registration by connection-string name for every transport
and every message store, timer agents for short-cadence recurring
schedules, and the 6.x groundwork for Wolverine 7.0's
one-chain-per-handler model. There are no breaking API changes. Two
behavior changes are worth knowing about up front: recurring schedules
that fire more often than every 15 minutes now run on a timer agent (opt
out per schedule with `UseScheduledMessages()`), and outgoing envelopes
a transport cannot map are dead-lettered instead of being logged and
deleted.
Many thanks to everyone who reported issues and sent fixes: @uniquelau,
@johnkaram02, @erdtsieck, @ahmetvayhan and @pritamrao.
## Aspire parity: named connections
* Named-connection overloads for every transport (Aspire parity,
GH-5126) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5159
* Message storage by connection-string name for every RDBMS provider
(PostgreSQL, SQL Server, MySQL, SQLite, Oracle), `NpgsqlDataSource` /
`MySqlDataSource` from the container, named static tenants, a Named
Connection Strings table in `describe`, and docs that lead with the
named form (GH-5127, GH-5156) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5174
* Aspire samples moved to Aspire 13.6.3, added to `wolverine.slnx`, with
a ServiceDefaults project for traces, metrics and `/health` (GH-5124) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5182
## Messaging and error handling
* Timer agents for short-cadence recurring schedules: one agent per
schedule under `opts.Schedules.TimerThreshold` (15 minutes), same dedup
id as the scheduled path, pause and resume through
`IRecurringScheduleControl` (GH-5168) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5183
* `DoThenRetry(action, maxAttempts)` error policy that works both inline
and from a queue (GH-5068) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5179
* Unmappable outgoing envelopes are dead-lettered on Azure Service Bus,
SNS and Kafka instead of being reported as sent (GH-5055) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5185
* Durable outbox coalesces stores and deletes per tenant database, so
one slow tenant no longer holds back every tenant's sends (GH-5171) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5180
* 6.x prep for HandlerChain 1:1 in 7.0:
`MultipleHandlerBehavior.ClassicCombineIntoOneLogicalHandler` is
`[Obsolete]` (still the default), a startup warning for combined
handlers, opt-in `AssertNoCombinedHandlers()`, fixes for latent
`Separated` bugs, a `handler.type` OpenTelemetry tag on every chain, and
logical deduplication scoped per chain (GH-5133) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5184
## HTTP
* `[DeduplicatedWithResponse]` `ClaimInTransaction`, plus two Marten
commit-failure fixes (GH-5160, GH-5165, GH-5166) by @jeremydmiller with
@uniquelau in https://github.com/JasperFx/wolverine/pull/5169
* `WolverineHttpOptions.DefaultClaimInTransaction`, an application-wide
default (GH-5160) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5173
* A string response declares its UTF-8 byte count as `Content-Length`
(GH-5187) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5188
* Bind array and list query parameters whose `[FromQuery]` name is not a
C# identifier (GH-5189) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5190
## Operability and diagnostics
* `ServiceCapabilities` reads each store on its own and reports
`FailedSections`, so a failed section is distinguishable from an empty
one (GH-5162) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5175
* `AssignmentGrid.Agent.UnplacedReason` (`AwaitingCapacity`,
`NoCapableNode`, `Paused`, `TakeoverHold`, `Superseded`), a capacity log
line and an optional `NodeAcceptThreshold` (GH-5163) by @jeremydmiller
in https://github.com/JasperFx/wolverine/pull/5176 and
https://github.com/JasperFx/wolverine/pull/5186
* Header-equality filter on `DeadLetterEnvelopeQuery`, honored by bulk
replay and discard, across every dead letter store (GH-5164) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5181
* `db-list` / `db-dump` / `db-patch` discover the Oracle message store
(GH-5172) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5177
## Documentation
* The remaining serverless and Cosmos DB documentation (GH-5054) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5178
* Memory sizing for managed event subscription distribution at tenants x
projections scale: set `MaximumHopperSize` to about 2 x `BatchSize`
(GH-5170, follow-up JasperFx/jasperfx#1076) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5199
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.51.3...V6.52.0
Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.51.3...V6.56.0).
</details>
Updated
[WolverineFx.RuntimeCompilation](http://github.com/jasperfx/wolverine)
from 6.51.3 to 6.56.0.
<details>
<summary>Release notes</summary>
_Sourced from [WolverineFx.RuntimeCompilation's
releases](http://github.com/jasperfx/wolverine/releases)._
## 6.56.0
## New
- **GH-5238** (#5242): `opts.UseHttpTransport()` registers the HTTP
transport inside `UseWolverine()`. `http`/`https` URIs then resolve in
`ListenForMessagesFrom()`, `PublishMessage<T>().To(uri)`, and runtime
`EndpointFor()` / `ToDestination()`. Before this, the transport was only
registered by `ToHttpEndpoint()`, and any other `http` URI failed with
`Unknown Transport scheme 'http'`. `/_wolverine/invoke` now answers 415
instead of 500 for a content type with no registered serializer.
## Fixes
- **GH-5235** (#5239): Host shutdown now stops the durability metrics
sweeper and waits for any query it has in flight before stores are
disposed. Removing a durability agent also waits for its in-flight
query. A metrics query could previously outlive `IHost.StopAsync()` and
throw `ObjectDisposedException` against a disposed `NpgsqlDataSource`.
- **GH-5236** (#5240): A Kafka listener with a durable inbox no longer
crashes the process with a stack overflow when the inbox database is
unavailable. `DeferAsync()` used to call back into the receiver inline,
so the failed inbox write and the defer recursed. A deferred Kafka
envelope now goes back to the receiver on its own task after a short
pause, and only while the listener is running. After a stop or
inbox-recovery pause, its uncommitted offset brings it back. Because
Kafka has no native requeue, a `Requeue` error policy on a Kafka
endpoint now redelivers after about one second instead of immediately.
- **GH-5237** (#5241): A CloudEvents HTTP endpoint now fails a
single-envelope send that the receiver rejects. It used to count the
send as delivered, so a durable endpoint aimed at `/_wolverine/invoke`
could delete an outgoing envelope that was never received. HTTP
transport responses are now disposed.
## 6.55.1
## Fixes
- **GH-5230** (#5232): A database operation batch cancelled by
Wolverine's own shutdown is no longer wrapped in
`DatabaseBatchCommandException` and logged as an Error. Genuine
SQL/connection failures are still reported.
- **GH-5231** (#5233): An `HttpEndpoint` aimed at `/_wolverine/invoke`
in its default (buffered) mode now delivers by posting each envelope
individually, instead of receiving a 415 for every batch and retrying
forever. `/batch/{queue}` and `.SendInline()` endpoints are unchanged.
## 6.55.0
## Highlights
**WolverineFx.Bobcat is out of alpha, on Bobcat 1.0.0** (#5222,
#5229). Bobcat 1.0.0 shipped tonight, and `WolverineFx.Bobcat` and
`WolverineFx.Bobcat.EventModel` now publish as stable **6.55.0** instead
of `-alpha.1`. `WolverineFx.Bobcat.Http` stays `6.55.0-alpha.1` until
Alba 9 ships stable, because a stable package cannot depend on Alba
9.0.0-rc.2. A spec project that `write-specs` generates with no
`--wolverine-version` pins the stable `WolverineFx.Bobcat` (#5222,
#5223).
**`MessageCausedBy` names your handler** (#5224, #5225).
`IWolverineObserver.MessageCausedBy` reported the generated handler
class
(`Internal.Generated.WolverineHandlers.StartTripHandler939323516`). It
now reports the handler you wrote (`TripService.StartTripHandler`), the
same string as the `message.handler` span tag, resolved at codegen time.
Code generated by an earlier version keeps working through the old
overload.
**CosmosDB sagas start with a conditional create** (#5221, thanks
@thechucklingatom). A second start message for an existing saga id used
to silently overwrite the running saga. It now fails with the
`SagaConcurrencyException` that Marten, Azure Blob and S3 already raise,
so one `OnException<SagaConcurrencyException>()` policy covers every
provider.
## Fixes
* GH-5217: `ListeningAgent.StartAsync` is serialized, and deliveries are
deferred to a drained `BufferedReceiver` (#5228)
* GH-5218: an unresolvable tenant is treated as tenant-scoped on the
single-envelope inbox write (#5227)
* GH-5220: Azure Service Bus listeners join their receive loop before
disposing the `CancellationTokenSource` (#5226)
* GH-5215: reserved-word schema names are quoted in the remaining RDBMS
and PostgreSQL SQL (#5216, thanks @marc-RocketRez)
* GH-5213: a later pause supersedes an earlier pause's restart (#5214)
* GH-5209: the dead letter history table round-trips with no schema
drift (#5212)
## Dependencies
* JasperFx 2.96.0 (jasperfx#1083: an Observed vs Derived `Origin` no
longer raises a `SourceDisagreement`)
* Weasel 9.47.0 (#5219)
* Bobcat 1.0.0
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.54.0...V6.55.0
## 6.54.0
## Highlights
**Dead letter failure groups** (#5209, #5210). Wolverine now
fingerprints every message as it moves to dead letter storage, so dead
letters can be managed one failure group at a time:
* The default `IDeadLetterFingerprinter` hashes the exception type and
the first stack frame from application code. Register your own to
replace it.
* `DeadLetterEnvelopeQuery` gains `FailureGroup`,
`ExceptionMessageMatch` (Exact / Contains), `TenantId` and `Archived`.
* A new `SummarizeAllAsync` overload groups by failure group, message
type or endpoint, with first and last failure times.
* `ReplayMatchingAsync`, `DiscardMatchingAsync`, `ArchiveMatchingAsync`
and `RestoreMatchingAsync` report how many dead letters they affected.
* A message that fails again after a replay reports its `ReplayCount`,
`LastReplayedAt` and `FirstFailedAt`.
* Archived dead letters drop out of summaries, queries and replays until
restored, and can be expired separately with
`DurabilitySettings.ArchivedDeadLetterRetention`.
* `IDeadLetters.Capabilities` tells you what a store supports.
All changes are additive. The relational stores pick up seven nullable
columns on `wolverine_dead_letters`, an index and a new
`wolverine_dead_letter_history` table through the normal schema
migration. See the [dead letter storage
guide](https://wolverinefx.net/guide/durability/dead-letter-storage.html).
**Deduplication fixes** (#5205, #5206, #5208), and **Bobcat
1.0.0-rc.4** dependency pins (#5211). The `WolverineFx.Bobcat*`
packages remain alpha (`6.54.0-alpha.1`).
## What's Changed
* GH-5205, GH-5206: [DeduplicatedWithResponse] claim-in-transaction gaps
([WriteAggregate] T / [BoundaryModel], and repeats refused over
committed work) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5208
* Bobcat 1.0.0-rc.4 by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5211
* GH-5209 Dead letters: failure fingerprints, operation counts, replay
history, archive/restore and tenant scoping by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5210
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.53.0...V6.54.0
## 6.53.0
## What's Changed
* GH-5202: keep the ASB response queue identity stable during startup by
@DeandreT in https://github.com/JasperFx/wolverine/pull/5203
## New Contributors
* @DeandreT made their first contribution in
https://github.com/JasperFx/wolverine/pull/5203
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.52.0...V6.53.0
## 6.52.0
Wolverine 6.52.0 is a feature release. The headline items are
Aspire-style registration by connection-string name for every transport
and every message store, timer agents for short-cadence recurring
schedules, and the 6.x groundwork for Wolverine 7.0's
one-chain-per-handler model. There are no breaking API changes. Two
behavior changes are worth knowing about up front: recurring schedules
that fire more often than every 15 minutes now run on a timer agent (opt
out per schedule with `UseScheduledMessages()`), and outgoing envelopes
a transport cannot map are dead-lettered instead of being logged and
deleted.
Many thanks to everyone who reported issues and sent fixes: @uniquelau,
@johnkaram02, @erdtsieck, @ahmetvayhan and @pritamrao.
## Aspire parity: named connections
* Named-connection overloads for every transport (Aspire parity,
GH-5126) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5159
* Message storage by connection-string name for every RDBMS provider
(PostgreSQL, SQL Server, MySQL, SQLite, Oracle), `NpgsqlDataSource` /
`MySqlDataSource` from the container, named static tenants, a Named
Connection Strings table in `describe`, and docs that lead with the
named form (GH-5127, GH-5156) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5174
* Aspire samples moved to Aspire 13.6.3, added to `wolverine.slnx`, with
a ServiceDefaults project for traces, metrics and `/health` (GH-5124) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5182
## Messaging and error handling
* Timer agents for short-cadence recurring schedules: one agent per
schedule under `opts.Schedules.TimerThreshold` (15 minutes), same dedup
id as the scheduled path, pause and resume through
`IRecurringScheduleControl` (GH-5168) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5183
* `DoThenRetry(action, maxAttempts)` error policy that works both inline
and from a queue (GH-5068) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5179
* Unmappable outgoing envelopes are dead-lettered on Azure Service Bus,
SNS and Kafka instead of being reported as sent (GH-5055) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5185
* Durable outbox coalesces stores and deletes per tenant database, so
one slow tenant no longer holds back every tenant's sends (GH-5171) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5180
* 6.x prep for HandlerChain 1:1 in 7.0:
`MultipleHandlerBehavior.ClassicCombineIntoOneLogicalHandler` is
`[Obsolete]` (still the default), a startup warning for combined
handlers, opt-in `AssertNoCombinedHandlers()`, fixes for latent
`Separated` bugs, a `handler.type` OpenTelemetry tag on every chain, and
logical deduplication scoped per chain (GH-5133) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5184
## HTTP
* `[DeduplicatedWithResponse]` `ClaimInTransaction`, plus two Marten
commit-failure fixes (GH-5160, GH-5165, GH-5166) by @jeremydmiller with
@uniquelau in https://github.com/JasperFx/wolverine/pull/5169
* `WolverineHttpOptions.DefaultClaimInTransaction`, an application-wide
default (GH-5160) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5173
* A string response declares its UTF-8 byte count as `Content-Length`
(GH-5187) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5188
* Bind array and list query parameters whose `[FromQuery]` name is not a
C# identifier (GH-5189) by @johnkaram02 in
https://github.com/JasperFx/wolverine/pull/5190
## Operability and diagnostics
* `ServiceCapabilities` reads each store on its own and reports
`FailedSections`, so a failed section is distinguishable from an empty
one (GH-5162) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5175
* `AssignmentGrid.Agent.UnplacedReason` (`AwaitingCapacity`,
`NoCapableNode`, `Paused`, `TakeoverHold`, `Superseded`), a capacity log
line and an optional `NodeAcceptThreshold` (GH-5163) by @jeremydmiller
in https://github.com/JasperFx/wolverine/pull/5176 and
https://github.com/JasperFx/wolverine/pull/5186
* Header-equality filter on `DeadLetterEnvelopeQuery`, honored by bulk
replay and discard, across every dead letter store (GH-5164) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5181
* `db-list` / `db-dump` / `db-patch` discover the Oracle message store
(GH-5172) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5177
## Documentation
* The remaining serverless and Cosmos DB documentation (GH-5054) by
@jeremydmiller in https://github.com/JasperFx/wolverine/pull/5178
* Memory sizing for managed event subscription distribution at tenants x
projections scale: set `MaximumHopperSize` to about 2 x `BatchSize`
(GH-5170, follow-up JasperFx/jasperfx#1076) by @jeremydmiller in
https://github.com/JasperFx/wolverine/pull/5199
**Full Changelog**:
https://github.com/JasperFx/wolverine/compare/V6.51.3...V6.52.0
Commits viewable in [compare
view](http://github.com/jasperfx/wolverine/compare/V6.51.3...V6.56.0).
</details>
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-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
</details>
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
* **Chores**
* Updated versions of platform and application components, along with
testing tools. Other package settings remain unchanged. These updates do
not add or remove user-facing features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Closes #5160. Closes #5165. Closes #5166.
This PR fixes all three open issues from @uniquelau. It builds on their PR #5161, and its three commits for #5160 are included unchanged, under their authorship. This PR supersedes #5161. Laurence Gillian is a co-author of the commit for #5165 and #5166.
Merge with
--merge, not--squash, so Laurence keeps authorship of the #5160 commits.#5160:
[DeduplicatedWithResponse(ClaimInTransaction = true)]This is #5161 as written: commits
282abccd1,2cfee6bedand148fd57b2, withmainmerged in. #5161's description covers the design. In short:#5161's open question (should this be automatic, as
[Deduplicated]is) is still open; this PR keeps it opt-in.#5165: a failure after Marten's database commit released the claim
Marten runs session listeners'
AfterCommitAsyncinsideSaveChangesAsync, after the database commit. Wolverine's outbox flush is one of those listeners. On the default claim-and-release path, a throw there left the work committed and released the claim, so the retry ran the endpoint again.DatabaseCommitMarker(new, Wolverine.Marten) is a session listener inserted at index 0 of the session's listeners. Marten runs listeners in list order, so it records the commit before the outbox flush or any user listener can throw.IDatabaseCommitHookProvider(new, internal) is a sibling seam toITransactionalReplayableDeduplicationProvider. Only Marten implements it. The HTTP chain uses it to enlist the marker, with a callback that setsHttpHandler.MarkDeduplicatedDatabaseCommitted.ReleaseUnansweredDeduplicatedResponseFramegets acatch { KeepDeduplicatedClaimIfCommitted(ctx); throw; }, but only when the hook applies. On a failure over a committed database transaction, the claim is kept, unanswered. Repeats get 409 until it expires instead of running the work again. That is the same outcome as a failed response record today, and the fix [DeduplicatedWithResponse] on Marten: a failure inside SaveChangesAsync after the database commit releases the claim, so the retry runs the work again #5165 suggested.#5166: a concurrent
[WriteAggregate]loser under[Deduplicated]got a 500RefuseDuplicateClaimAtCommitFrameonly matches a unique violation on the deduplication table. A concurrent[WriteAggregate]loser fails on the stream version before it reaches the claim's INSERT, so it escaped as a 500.RefuseDuplicateMartenClaimAtCommitFrame. It keeps Deduplication should ride the native transaction on Marten, Polecat and Fisher — batched optimistic check, no compensating release #4505's classifier catch, and adds a second catch that runs only while the commit is in progress:CommitStarted && !Committed, from the sameDatabaseCommitMarker.MartenDeduplicationFailures.IsClaimedAfterFailedCommitAsync) whether the key is now claimed.IMartenDeduplicatoris untouched; the lookup is a static onMartenDeduplicationFailures.Polecat probably has the same gap as #5166 (it uses the same core frame), but it is not touched here.
Tests
New tests:
deduplicated_with_response_on_marten([DeduplicatedWithResponse] on Marten: a failure inside SaveChangesAsync after the database commit releases the claim, so the retry runs the work again #5165):deduplication_on_a_marten_transaction([Deduplicated] on Marten: a concurrent [WriteAggregate] repeat gets a 500, not the 409, when its commit fails on the stream version first #5166):[WriteAggregate]race returns[200, 409], with one event and one claim.Mutation checks, each applied and then reverted:
RefuseDuplicateClaimAtCommitFrame, the [Deduplicated] on Marten: a concurrent [WriteAggregate] repeat gets a 500, not the 409, when its commit fails on the stream version first #5166 race test fails.!Committedguard, the after-commit test fails.Run locally:
dotnet build wolverine.slnx -c Release -f net9.0: 0 errorsFullyQualifiedName~eduplicat: 101/101FullyQualifiedName~eduplicat: 17/17Docs:
docs/guide/http/deduplication.md(claim-and-release on Marten) anddocs/guide/durability/idempotency.md(the[WriteAggregate]race).🤖 Generated with Claude Code
https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U