Table of Contents

Release 2026-08-18

This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at 2026-08-18.md, and llms.txt lists every page.

Part of the changelog.

A per-package patch advances Orleans.Lattice.Replication to 9.0.2 and Orleans.Lattice to 9.0.5 (see Changed and Fixed); all other packages remain at their 9.0.x/9.0.0 versions.

Changed

  • MvRegister writes and single-valued reads no longer allocate on the steady-state hot path. A write (Set) previously built a fresh List plus its backing array on every call, and a single-valued read (Values(), which the typed MvRegister<T> accessor read path funnels through) allocated a new one-element array on every read. Set now compacts its live entries in place, and the single-valued read snapshot is cached and reused between writes, so a steady-state (single-valued) register - the overwhelmingly common shape - writes and reads with zero per-operation allocation. No public API, behaviour, or wire-format change; ordering and conflict semantics are identical. (Orleans.Lattice 9.0.5, #1509, #1510)

Fixed

  • Replicated cross-tree atomic writes now converge on peer clusters backed by Azure Table grain storage instead of stalling. The receiver-side barrier grain for a replicated cross-tree atomic write (LatticeCrossTreeReceiverGrain) is keyed by a compound of (originClusterId, operationId). That key was built by joining the two halves with the ASCII Unit Separator (0x1F) control char - chosen so neither half could be confused with the other - but the receiver grain persists through a storage provider, and Azure Table grain storage carries the primary key into the request URL and the Partition/Row key columns, both of which reject control chars 0x00-0x1F. On a real Azure deployment the receiver therefore failed to activate with an HTTP 400 "Bad Request - Invalid URL" on ReadStateAsync/WriteStateAsync, so the receiver barrier for cross-tree atomic writes stalled and those writes converged across regions only slowly and erratically; single-tree atomic writes and plain writes were unaffected. The whole test and chaos suite stayed green because in-memory grain storage has no key-character restriction, hiding the defect from CI. The key is now a length-prefixed encoding (the decimal length of originClusterId, an underscore, then the two halves concatenated), which stays a single deterministic, control-char-free string while remaining unambiguous regardless of the characters in either half. Changing the key changes the receiver grain's identity, which is acceptable because the Azure Table path was non-functional and had no durable state to preserve. (Orleans.Lattice 9.0.5, #1529)
  • Cross-tree atomic writes now become visible on peer clusters instead of hanging invisibly forever. A cross-tree atomic write (IGrainFactory.SetManyAtomicAsync(IReadOnlyList<LatticeTreeBatch>, operationId, ct)) committed on its origin cluster but its keys never appeared on peer clusters, while single-tree atomic writes and plain writes on the same trees converged normally. The receiver's multi-entry batch apply path (ReplicationApplier.ApplyBatchAsync -> ApplyOriginRunAsync) had no case for saga terminal-mark records (TxCommit / TxAbort): unlike the single-entry ApplyAsync path, which intercepts terminals and routes them through the terminal seam, the batch path let a terminal fall through to ApplyPointAsync, whose op switch threw Unsupported point-apply op. Because the production shipper coalesces a saga's contiguous WAL entries - its prepared writes and its terminal - into a single inbound batch, the terminal arrived batched, the whole batch faulted on apply, and the cross-tree receiver barrier (which holds every participating tree's keys pending until a terminal has arrived for each) never released, so the peer's keys stayed invisible indefinitely. The batch path now intercepts TxCommit / TxAbort and routes them through the same terminal seam as the single-entry path (flushing any deferred LWW/CRDT batch first so the terminal linearizes in WAL order, and staying high-water-mark-neutral), forwarding the CrossTreeOperationId / CrossTreeParticipants barrier metadata intact. Single-tree atomic convergence and local idempotency semantics are unchanged. (Orleans.Lattice.Replication 9.0.2, #1525)