Skip to content

Marten 9.46.0

Latest

Choose a tag to compare

@jeremydmiller jeremydmiller released this 04 Oct 23:51
· 11 commits to master since this release

Mostly a resiliency and Native AOT release. Five behaviour changes — the retry ones are the most likely to be noticed, because Marten now surfaces failures it used to absorb.

Behaviour changes

A command timeout is no longer retried on reads (#5566, #5580). An attempt that timed out has already spent the whole CommandTimeout, so retrying it three times held the caller for four timeouts instead of one. This covers both shapes: Npgsql's client-side CommandTimeout, and a server-side statement_timeout, which arrives as 57014 query_canceled with no TimeoutException anywhere in the exception. Connection-pool exhaustion arrives in the same shape and so is no longer retried either — a deliberate trade, documented in the resiliency guide along with what to do instead.

Sticky, ambient and external session lifetimes now fail loudly (#5582, #5598), via the new SessionTransactionUnusableException. Two things used to go wrong silently. A broken connection reported ConnectionState.Closed, so Marten reopened it — onto a different backend — and handed the command the stale transaction, which Npgsql accepts; a Serializable session then quietly answered from a newer snapshot. And a command timeout inside a transaction aborted it, after which every later call returned a bare 25P02 naming neither the timeout nor the way out. For caller-owned transactions Marten records the cause and reports it but rolls nothing back — the scope is still yours.

The async serializable session builders now produce a serializable session (#5594). OpenSerializableSessionAsync(new SessionOptions()) previously returned a session that was neither serializable nor auto-closing: it held one connection open for its lifetime with no transaction. These two methods now pin IsolationLevel to Serializable and begin the transaction eagerly. A non-Serializable level passed to them is overridden rather than silently honoured.

The async daemon spends one retry budget instead of two (#5593). ResilientEventLoader and the QuerySession inside it were both given the same pipeline, so a handled failure ran up to sixteen times.

A retried write no longer accumulates exceptions across attempts (#5591). The exception list was shared, and ExecuteBatchPagesAsync branches on its count — so one operation failing twice turned a rethrow into an AggregateException, making the caller's exception type depend on how many retries happened.

Native AOT

Strong-typed document ids now work (#5589, #5579), with one registration per document type:

opts.Schema.For<Invoice>();
opts.RegisterValueTypeId<Invoice, InvoiceId, Guid>();

Previously a single strong-typed id anywhere in the store killed the application while the first document's mapping was built. The identity path closes a chain of generics over your id type, and a native image has no instantiation for them unless something roots it; naming all three types is what roots them. Under a JIT the call does nothing — unregistered types still take the reflective path. Not yet covered: F# single-case discriminated-union ids, and .IsOneOf over a strong-typed id. The intent is for Marten.SourceGenerator to emit these registrations so the call becomes unnecessary.

Also AOT: a decimal comparison in a LINQ filter is documented with a tested workaround (#5597) — it is the BCL expression-tree factory rather than Marten, but it only shows up from a native binary. Marten's AOT runtime gate now covers strong-typed ids and decimals, so these stay pinned.

Fixes

  • PrefixSearch quotes each word as a tsquery lexeme (#5571, #5568), so a search box containing &, |, !, (, : or a tab no longer produces 42601: syntax error in tsquery. Not an injection — the term was already a parameter — but the documented contract says each word is treated as a prefix, and raw input has to be escaped to honour that.
  • A quick append nulls every DCB tag slot, not just the tagged ones (#5570).
  • AdvisoryLock shutdown release is bounded, and the vendored copy moves back to Weasel (#5572, #5576).
  • Three catch-and-Console.WriteLine-and-rethrow blocks removed (#5584) — they bypassed IMartenSessionLogger and any configured ILogger, and one of them also closed a connection on the line above the one that opened it.

Dependencies

JasperFx 2.80.2 (#5590) and Weasel 9.41.0 (#5596, #5576). Both carry Native AOT work of their own on the identity path — JasperFx's ValueTypeInfo wrappers and aggregate identity sources, Weasel's non-generic IIdentification facade.

🤖 Generated with Claude Code

https://claude.ai/code/session_01481eiEZg1Bv6pu4DrgPRg3