Repository navigation
Stratara 4.2.0
A hardening release for the Orleans execution model, and a new test-support package for it.
Stratara.Testing.Orleans runs the execution model inside the test's process. Beyond that, 4.2.0 closes
every gap known before release. A recorded command runs once and in its scope's order, and it is counted
against the bus's two bounds. A store reader paused by a rebuild comes back if its pauser dies, and the
rebuilt read model keeps every fact. A failing saga stops only itself. Every options section the framework
documents is read from configuration, and an anonymous caller is answered 401.
Upgrading:
- Generate an EF Core migration. The intent record gains a
conflict_countcolumn. - Check
appsettings.jsonforSessionContext:AllowTenantHeaderand
Stratara:BlobEncryption:LegacyBlobsCarryPurpose. Both were ignored until now and take effect with
this release. - Upgrade the saga silos together.
- Start a rebuild only once every silo runs 4.2.0.
Added
-
IProjectionCheckpointStore.FindAsyncandCreateAsync: a checkpoint's position, ornullwhere none exists, where
GetAsyncanswers0for a missing checkpoint and one at the beginning alike; and a first checkpoint written only where none exists, never replacing one. The
framework's store implements both; the defaults throwNotSupportedExceptionnaming the members, so a store of the
consumer's own keeps compiling and fails only where a store-reading saga needs them. -
New package
Stratara.Testing.Orleans(test-support, the 28th package):ExecutionModelTestHostruns the Orleans
execution model in a test's own process — one silo, in-memory reminders and grain directory, the real write stack,
portable commit-order reader, checkpoint and intent stores on in-memory SQLite, and every period shortened to
seconds. A test registers its roles with the production calls, dispatches, waits for the readers
(WaitForReadersAsync) and asserts;Timers,SeedAtHeadAsync,ResetAsyncandExecutionModelTestHostOptions
(withBeforeStart) complete it. The package carries theSTRATARA1001build check and refuses a stated non-development
environment. -
Sample
Stratara.Sample.OrleansExecutionModel: a command in its aggregate's activation, a projection from the
store and a process timeout on the test host, in one console run; smoke-tested. -
AddStrataraTestingEventStore<TWriteDbContext>(connectionString, tenantId, configureContext): a connection per
context to a shared database, and the caller's options — an interceptor — after the provider. -
AddCommandServices()(Stratara.EventSourcing.WorkerDefaults): the command worker stack without the bus-fed
mediator worker, besideAddEventProjectionServicesandAddSagaServices.AddCommandWorkerServicesis now
AddCommandServicesplus the worker. A silo composed with it, the execution model's command dispatcher and intent
store, the aggregate grains and a store-reading role runs commands and applies their facts without a message broker,
as does an API host that only dispatches through the execution model's dispatcher. -
Orleans:
AddStrataraSingletonWork<TWork>(string name, ...)andOutboxDrainWork.WorkName. A work registered
with its name is published in the silo's metadata without being constructed, so it is first constructed when the
silo is active; a work whoseNamediffers from the registered name fails the start naming both. -
Orleans: log event
117_119(LogEvents.Orleans.SingletonWorkFailed) for a run of a singleton work that threw;
the work runs again at its next period, as before.117_120(LogEvents.Orleans.RolesUnpublished) for a silo
that does not start because it hosts roles or work it does not publish. -
Orleans: a recorded command is signed and verified. Where the host registered a bus-envelope signer, the record
the execution model writes before a dispatch returns carries the signature a bus command carries, and the drain
verifies it underBusEnvelopeIntegrityOptions.Modebefore resuming it: underStrictan unsigned or invalid record
is kept at once with the reason, underPermissiveit is resumed and logged. Log events117_115–117_118. -
ICommandIntentStore.ClaimAsyncclaims a batch of due commands; the default claims row by row through
TryClaimAsync, the shipped store in two statements. -
Orleans: log event
117_008(LogEvents.Orleans.HandlerStoppedWithSilo) for a handler on a grain path cancelled
because its silo stopped. -
IProjectionCheckpointStore.AdvanceAsyncadvances a checkpoint from the position its writer last saw. The
default replaces the position throughSetAsync, so a store of the consumer's own keeps compiling; the shipped
store refuses a write that finds another position or another reader. -
Orleans: log events
117_005–117_007—StoreReaderRetiredfor a reader beyond the host's partition
count,ResumeHeldBackByReplayandResumeReleasedAfterReplayonce each when a full replay holds recorded
commands back and releases them. -
Orleans: a host seeds its store readers at the head.
IStoreReaderSeeding.SeedAtHeadAsync, registered
withAddStrataraProjectionGrainsandAddStrataraSagaGrains, writes a checkpoint at the store's current
head for every registered projection and saga and partition that has none, and reports how many it wrote and
how many existed. A host whose read models are current runs it once, while no silo runs, before its first
start — without it the first start re-applied the whole history. A consumer registered later still starts
at the beginning. -
ICommittedPositionReader.HeadAsyncreports a partition's head — the position after which nothing
committed exists — with a default that walks the partition; both shipped readers answer in one query. -
Orleans:
CommitTransactionIdBackfillstamps the commit record on a populated PostgreSQL event table in
append order and bounded batches, so the native reader's column is added without rewriting the table and
without the whole history landing under one transaction id. -
Orleans:
AddStrataraExecutionModelResettakes the schema the runtime tables live in.schema
defaults topublic; a reminder or membership table absent under it fails the reset naming the table
instead of reporting that nothing was removed, and the three runtime tables are cleared in one transaction. -
Orleans: two log events.
117_112(LogEvents.Orleans.IntentAttemptFailed) for every attempt of a
recorded command that fails, with the command's id, type and aggregate;117_113(HandOverFailed) for a
hand-over that fails. A failing handler is seen before its command is kept. -
Orleans: log event
117_114(LogEvents.Orleans.PermitReclaimRefused) for a running heavy unit that was
refused its permit when it registered again after the permit keeper was lost, and runs outside the
cluster-wide bound until a permit is free. -
SessionRequiredException(Stratara.Abstractions.Session): the failure of an operation that must be
attributed to a caller and runs with no session context — a save, a command audit, a dispatch on the bus path and
on the execution model's path. It derives fromInvalidOperationExceptionand keeps the message these operations
always used, so an existingcatch (InvalidOperationException)and a log search on the text keep working; a host
catches it without referencing any store or broker package. -
ICommandIntentStore:RecordAsync(…, recordedAt, …),TryRenewAsync,TryRenewFromAsync,
RecordConflictAsyncandReturnAttemptAsync, each with a default that does what a store written against 4.1
did — the record's time is the store's own and its count starts at zero, every hand-over runs, a conflict is
recorded as a failure, a stop gives nothing back.
OutboxEntry.ConflictCountandRecordedIntent.ConflictCount(defaulted) carry the conflict count.
Changed
-
Orleans: each store-reading saga reads with a checkpoint of its own. A saga's reader is keyed and checkpointed
undersagas:<SagaName>— the saga's type name — instead of the consumersagasall sagas of a deployment shared,
soorleans.reader.stalled, the stall log117_101and the attempt log117_102carry the failing saga's consumer
name; a dashboard or alert that filters onsagasmust be widened to thesagas:consumers. Seeding, the
execution-model reset and the wake-up after a commit name every saga's consumer, and the reset also removes the
sharedsagascheckpoint of 4.1.x. A saga without a checkpoint starts where the deployment's sagas read in that
partition — the shared checkpoint of 4.1.x, or else the furthest checkpoint another saga holds — so a saga added
to a running deployment runs no side effect for the store's history. Upgrade note: nothing is migrated; on the
first start every saga starts at the shared checkpoint, and the shared reader, brought back by its 4.1.x keep-alive,
retires and logs117_125(information). While a 4.1.x saga silo runs beside a 4.2.0 one both apply the facts
above the shared checkpoint, so upgrade the saga silos together. A rollback to 4.1.x resumes the shared reader
from the shared checkpoint, which 4.2.0 never advanced, and applies what the per-saga readers applied since
again. Seeding a store with the shared checkpoint starts each saga there rather than at the head, so the shared
reader's backlog is not skipped. A host that registers two saga types of the same type name — in different
namespaces — no longer starts, naming both, because they would share one checkpoint; rename one before upgrading.
A saga's reader brought back on a silo that registers no saga of its name — a removed or renamed saga — retires,
unregisters its keep-alive and logs117_126(information) instead of stalling on every entry. A host with a
checkpoint store of its own must implementIProjectionCheckpointStore.FindAsyncandCreateAsync(see Added)
to run store-reading sagas. -
Security:
"SessionContext": { "AllowTenantHeader": true }in the host's configuration now turns the tenant-header
fallback on.AddSessionContext()did not read theSessionContextsection, so such an entry did nothing unless
the host bound the section itself; it is read now (see Fixed). Before upgrading, search every environment's
configuration forAllowTenantHeaderand remove the entry or set it tofalseunless the host means to accept the
X-Tenant-Idheader. Likewise, aStratara:BlobEncryption:LegacyBlobsCarryPurposeentry now takes effect for a host
that registers the blob encryptor throughAddSecurity()orAddStrataraBlobEncryption()alone. -
AddStrataraProblemDetails()answers a caller that is not authenticated with 401. An authorization refusal, a
tenant-access denial and aSessionRequiredExceptionalike: what such a caller lacks is an identity, not a
permission. Where the host has a default authentication scheme the mapping challenges through it — a bearer client
receivesWWW-Authenticate, a cookie client is redirected, as[Authorize]would do — and writes the problem body
where the challenge leaves a 401; without a scheme it writes a 401 problem response. A client that treated 403 as
"log in" now receives 401, which is the status that says so. An authenticated caller's denial stays 403, and a
SessionRequiredExceptionfor an authenticated caller is not converted — it means the host did not run the
session middleware. A host that does not register the mapping sees nothing new but the exception type. -
Orleans:
hybrid: truewithout a bus dispatcher fails at registration namingAddOutboxDispatcher; it ran
grain-only without a word. -
The execution model's operating limits are documented: a forwarded command whose handler outlasts the response
timeout fails its caller and commits (do not retry on a timeout); a resumed command is authorized from its recorded
session, so the authorization provider answers from the session, not the web request; the record of a command is
committed on its own, not with the caller's writes; a full replay applies the store twice to store-reading
projections; singleton work may overlap on two silos for one membership refresh after a death declaration, and the
failover latency follows from the membership settings.ISingletonWorkandIAuthorizationProvidersay so in their
XML documentation. -
Orleans: a silo places grains on itself by the roles and work it publishes, before its own metadata reaches its
cache. A silo that became active and started its store readers at once could fail its start with No silo of the
cluster registered the projections role, because its own metadata was not yet known to it. -
Orleans: a wake-up nudge that cannot be sent no longer fails the dispatch after a commit. A commit made before the
silo started, or while it stopped, threw from the bundle dispatcher although the facts were stored; the nudge is now
lost like any other and the readers' poll applies the facts. -
The portable commit-order reader is verified on SQLite as well as PostgreSQL, through the test host.
-
Migration guide: the command silo and the bus queues. The command role's row names
AddCommandServices, the
dispatcher and the intent store; When the broker can go states which hosts need the broker; After the cut-over:
the bus queues names the queues the bus workers own and the order in which to retire them. -
Orleans: every registration of the execution model is idempotent. A second call of
AddStrataraProjectionGrains,AddStrataraSagaGrainsorAddStrataraSingletonWork<TWork>registered its wake-up
target or its work again, so every projection was woken twice per bundle and the seeding and reset listed each
consumer twice. -
Orleans: a silo that hosts a role or singleton work without publishing it fails at start. A silo that registered
the grain directory under the model's name itself rather than withAddStrataraOrleansstarted and was placed on as
if it hosted every role; it now fails naming the roles and works it found andAddStrataraOrleans. A silo that
hosts nothing placed by role is unaffected. -
Orleans: the drain resumes a backlog as fast as the handlers take it. While a pass finds a full batch due, the
next pass follows at once, for at mostOutboxDrainOptions.PollingInterval; a backlog was resumed one batch per
period. -
Orleans: a stopping silo tells the handlers on its grain paths. Command handlers in an aggregate's activation or
a runner, heavy work and timer handlers receive aCancellationTokenthat is cancelled once the silo has been
stopping forGrainCollectionOptions.DeactivationTimeout; it wasCancellationToken.None. A recorded command
stopped this way is resumed elsewhere with no attempt counted, a forwarded command fails back with a message saying
the silo stopped, and a timer stays registered and fires on the next silo. -
Orleans: a timer registered from its own handler is kept. A handler that registered its owner and purpose again
with the same due time lost the new timer when its tick unregistered itself. -
Orleans:
IDurableTimersrefuses an owner id longer than the reminder table holds (139 characters) on every
member, with a message naming the limit, instead of failing at the first tick. -
Orleans: the heavy-work bound holds across the loss of the silo keeping the permits. A permit keeper
that is activated admits no new heavy unit for onePermitLeaseand takes back every running unit that
registers again, so the units the lost keeper had admitted count against the bound before new ones start;
previously a new keeper handed out the whole bound at once beside them. A running unit whose permit was lost
now reads the answer when it registers again and keeps asking until it holds a permit. The first heavy
commands after a keeper's activation — including a cluster's first — start one lease later. -
The migration guide says how a populated store adopts the execution model. The transaction-id column
is added nullable, backfilled, then given its default and constraint — while nothing appends; the guide
states the upgrade order (schema before the first 4.1 host, 4.0 hosts keep running, bus outbox worker
before the first drain silo, seed, silos, API host, bus workers, queues), that a checkpoint is keyed by the
projection's simple class name, and the prerequisites of the Orleans packages. -
Orleans: every role is placed on the silos that registered it. A silo publishes the roles its
composition registers — commands withAddStrataraAggregateGrains, projections with
AddStrataraProjectionGrains, sagas withAddStrataraSagaGrains, timers withAddStrataraDurableTimers
and anITimerOwners— and aggregates, command runners, heavy-work pools, projection, saga, process and
timer-owner grains are placed only on silos publishing their role, as singleton work already was. Roles
may be split across silos; a call for a role no silo registered fails naming the role and the registration.
The heavy-work pool is no longer a stateless worker on the calling silo: it is one pool per eight permits
ofHeavyWorkOptions.ClusterWideLimit, placed on silos of the command role. An API host that only
dispatches may join as an Orleans client. -
Orleans: a send that would close a cycle between aggregates is refused at once. A handler running for
aggregate A that sends to B, whose handler sends back to A, used to wait for the runtime's response
timeout; the send back is now refused with a message naming both aggregates, and both commands fail at
once. Sends between aggregates form a directed acyclic graph. -
Orleans: a read that fails counts as a stall. A store that cannot be read, or a checkpoint the reader
refuses, is logged as117_103and counted inorleans.reader.stalledwhichever wake-up or poll started
the read, not only on a nudge. -
Orleans: the portable commit-order reader states its preconditions. It is verified on PostgreSQL,
and through the test host on SQLite; every process that appends to a store it reads needsPartitionCounterInterceptor— the framework
does not add it, andCommitOrderOptions.MaintainPartitionCounteris only the value a write context reads
when it does; and the partition count cannot change once the store holds positions. On PostgreSQL the
native reader remains the one to use. -
PartitionCounterBackfillpositions a batch with one statement on PostgreSQL instead of one per entry;
other providers keep the per-entry update, and the positions it hands out are the same. -
Orleans: a recorded command is counted the bus's way — schema change. The record counts the hand-over of the
dispatch as the first ofMessageRetryOptions.MaxDeliveryAttemptswhen it is written, so a handler that keeps
failing runs as often as the bound says (before, once more), and a host that dies before that hand-over has used
the attempt, as a crashed delivery does on the bus — under a bound of 1 such a command is kept for an operator
without running. A concurrency conflict —ConcurrencyException, what the bus transports count as one — gives its
attempt back and counts againstMessageRetryOptions.MaxConflictRequeuesinstead (before, against the delivery
bound of 3). A stop gives its attempt back, for the running handler and for the commands queued behind it. The
count lives in the new columnoutbox_entry.conflict_count: generate and apply an EF Core migration before the
first 4.2 host starts, as for 4.1 (see the migration guide). A command recorded under 4.1 carries no counted first
attempt and runs as 4.1 ran it. An operator returning a kept command also resetsconflict_countand
last_failure.
Deprecated
CommitOrderOptions.MaintainPartitionCounteris obsolete: the framework never read it. A write context that
maintains the partition counter addsPartitionCounterInterceptor. The member is removed with the next major.
Fixed
-
Orleans: a resumed command runs once however its resumptions meet. Two claimers stamping the same millisecond
— the singleton drain during a failover, a bus outbox worker beside the drain during adoption — both handed the
command over, and a command naming no aggregate, or one whose first run had ended, ran twice; so did a hand-over
arriving after the command completed. The hand-over now carries the claim's stamp and runs only if its receiver
takes the record over from that stamp; one that finds it moved or the record gone is dropped and logged as
117_124(IntentHandOverDropped, Debug). -
Orleans: commands one scope dispatches to one aggregate are resumed in dispatch order. The record's time was
taken after the payload was serialized, so a slower first record resumed after the second —SetPrice 10after
SetPrice 20. It is now taken when the dispatch starts, strictly increasing per aggregate within the scope, and due
commands are resumed by that time, then by id. -
Orleans: a silo that stops no longer takes an attempt from the commands it was running or had queued. The
stopped handler, and every recorded command queued behind it that the stop gives up, gives back its attempt, so
rolling deploys during a long handler no longer keep commands for an operator. -
Orleans: a hand-over the fence dropped no longer holds its command. An aggregate's activation refused a later,
valid hand-over of the same command while the dropped one waited in its queue. -
Orleans: a dispatch's hand-over that arrives after a resumption completed the command is dropped. A hand-over
arriving later than a sixth of the grace checks that its command is still recorded and not kept. -
Stratara.Testing.Orleans: a recorded command is resumed on the test host. Its SQLite write store kept points
in time as text, which the intent store's due query cannot compare; they are kept as numbers now. -
Orleans: a recorded command's time comes from the registered
TimeProvider. It was the wall clock while the
drain compared it with the registered clock, so a host or test with its own clock saw commands never become due, or
become due at once. -
Orleans: a failing saga no longer repeats its siblings or stops them. All store-reading sagas of a partition
shared one reader and one checkpoint, so an entry one saga failed on was retried for every saga on each poll and
wake-up, without a bound — a saga that had succeeded on it, sending an email or issuing a command, did so again
every few seconds until the failing one was fixed — and every later fact of the partition waited for it. A saga
that fails now stops only its own reading of the partition; every other saga applies the entry once and goes on. -
An anonymous caller reaching an unguarded save or dispatch is no longer a server error. The event source, the
command audit, the bus dispatcher and the execution model's dispatcher threw a plainInvalidOperationException
that the problem-details mapping let through as a 500 reading like a framework bug; they now throw
SessionRequiredException, which the mapping answers 401 for a caller that is not authenticated.
RequireAuthorization()on every endpoint is no longer needed to avoid that. -
Orleans: a singleton work's settings apply to that work alone. The callback given to
AddStrataraSingletonWork<TWork>(configure)andAddStrataraSingletonWork<TWork>(name, configure)configured one
settings object shared by every work, so two works registered with keep-alive periods of two and five minutes both
ran with whichever callback ran last. Each work now runs with the host'sSingletonWorkOptions— what
services.Configure<SingletonWorkOptions>(...)sets — with its own callback applied on top, and a work registered
twice runs with the later registration's callback, once, rather than with both. Each work's settings are validated
when the host starts. A host that relied on one work's callback to configure the others sets the value for all
of them withservices.Configure<SingletonWorkOptions>(...)instead; a host that already configures
SingletonWorkOptionsdirectly is unaffected. -
Every options type that names a configuration section is read from it.
SessionContextOptions(SessionContext),
ProjectionReplayOptions(ProjectionReplay) and — for a host withoutAddStrataraFileKeyStore—
StrataraBlobEncryptionOptions(Stratara:BlobEncryption) were registered and bound by nothing, so a value in the
documented section did nothing.AddSessionContext(),AddProjectionReplayState()andAddStrataraBlobEncryption()
now read the section from the host's configuration, directly or throughAddOutboxDispatcher(),AddSecurity()and
the worker composites. A service collection without anIConfigurationkeeps the defaults; a value configured in
code after the registration takes precedence, and calling a registration again does not re-apply the section over
it.AddStrataraOrleansCommandDispatcher()likewise readsMessageRetryOptions(MessageRetry) and validates it at
start when no bus transport is registered before it — a host whose commands run only through the execution model
set the resume bound in that section to no effect; where a transport registered first already reads the section, it
stays the only reading. A test now holds every public options type with a section name to being read from it. -
A projection replay lease of zero or less is refused at start.
ProjectionReplayOptions.LeaseSeconds <= 0fails
the host with anOptionsValidationExceptionnamingProjectionReplay:LeaseSeconds; it was accepted, and on the
in-process replay state the marking lapsed at once and publication resumed in the middle of a rebuild. -
Orleans:
hybrid: truekeeps a bus dispatcher registered by factory or as an instance.AddStrataraProjectionGrains
andAddStrataraSagaGrainskept publishing bundles to the bus only when the dispatcher had been registered by type; a
factory- or instance-registered one was removed and bundles stopped reaching the bus. -
Orleans:
PartitionCounterBackfillno longer renumbers positioned entries. Unpositioned entries take the
positions after the partition's counter, so a checkpoint written before a backfill stays true; previously the
backfill shifted every positioned entry and a resumed reader re-applied one entry and lost another. -
Orleans: the portable reader stops at its partition's unpositioned entry however many other partitions hold.
The probe was cut at 64 unordered rows of any partition. -
Orleans: positions within one save follow each stream's version order, by contract rather than by the change
tracker's enumeration. -
Orleans: a store reader applies each entry under its own tenant. A projection, a saga and the services they
depend on are resolved after the entry's session is set — one scope per run of entries recorded under one
session — so a dependency that takes the tenant when constructed no longer sees the first entry's tenant, or
none, for a whole read. A process reads its state under the session of the fact it is handed. -
Orleans: a checkpoint is no longer rewound by a stale activation, nor written under another reader's name;
the refused reader reads the checkpoint again.SetAsyncof the shipped store refuses a row held by another
reader. -
Orleans: a reader of a partition beyond a lowered partition count retires instead of returning every
keep-alive period to be refused and counted as stalled. -
Orleans: an aggregate's order runs to the end however long it takes. The grain ran its accepted
commands through an ordinary call to itself, which the runtime timed out afterMessagingOptions.ResponseTimeout
(thirty seconds); the grain read the timeout as "the run never started" and failed every command still
waiting back to its caller, dropping the leases of the recorded ones. The call is one-way now; the
response timeout bounds a caller's wait for one forwarded command only, which the operations guide says. -
Orleans: a running command is renewed whether or not its handler yields. The lease and permit
renewals ran on the activation's scheduler, so a heavy handler that computed without awaiting past
IntentGracewas never renewed, was claimed by the drain and ran a second time. Both renewals run from
timers of their own. -
Orleans: a heavy hand-over is leased while it waits for a worker. A burst that queued units for
longer than the grace handed the queued ones over twice; the pool accepts a hand-over at once and its
lease covers the wait for a slot and a permit. A hand-over the pool already holds is not accepted twice. -
Orleans: two rebuilds of one projection no longer interleave. The pause was a flag: the first rebuild's
resume let the readers re-read and advance their checkpoints before the second rebuild truncated, which
left the read model empty for good. Pauses are counted, the readers resume when the last pauser resumes,
and a rebuild requested during a full replay is refused naming the replay. -
Orleans: the reset is resolved from a scope in the documentation and its example. It is a scoped
service; resolving it from the root provider throws wherever scope validation is on. -
Orleans: a batch cut short by the reader's shutdown still records the checkpoint for the entries it applied.
-
Orleans: the migration guide's package list compiles. It names
Microsoft.Orleans.Clustering.AdoNet,
Microsoft.Orleans.Reminders.AdoNetandMicrosoft.Orleans.GrainDirectory.Redis, where their SQL scripts
are, that the bus outbox worker is retired before the first drain silo with an intent store (the intent
store still claims stored bus commands older than the grace), that the saga role needs a reminder service,
and which settings the drain silo reads from its own configuration. TheStratara.OrleansREADME quick
start no longer keeps the bus-fed projection worker beside the grains; the log events117_001–117_113
and theorleans.*instruments are on the reference page and in the operations guide with what to alert
on; two pages that still said there is no checkpoint store say where one is. -
Orleans: the portable reader stops at an entry without a position instead of skipping it. An entry
appended by a process without the interceptor was never read, silently. A read of its partition now fails
naming the entry, the interceptor andPartitionCounterBackfill; the partition stops like any failing
entry and continues once the entry is positioned. -
Orleans: a host refuses to start with a partition count lower than the store's counters. Lowering the
count merged partitions whose positions overlap, and new appends were skipped even after the checkpoints
were reset. -
Orleans: the partition counter interceptor releases its transaction when positioning fails. The
transaction it opened stayed open on the context until the next save or the context's disposal. -
Orleans: both backfills of a populated store page by key. Each batch of the transaction-id backfill asked
for the oldest entries without a commit record, which no index answers, so every batch walked everything the
batches before it had stamped — on a store with a long history the migration window it belongs to was measured in
hours. The partition counter's backfill held a partition's whole history in memory and its counter lock for the
whole run; it now takes one batch per transaction. -
Orleans: the head says what it is. A head is taken while nothing appends: the native reader's head is held
back by an open write transaction, so a deployment that seeds while its old hosts still append applies what they
commit in that window a second time. The contract, the migration guide and a test now say so. -
Orleans: a singleton work's failure is logged whatever it failed with — a cancellation the work was not
asked for, an HTTP client's timeout, went unlogged — except where its silo is stopping. -
Orleans: two singleton works under one name are refused — at registration where the name is given, and when
the silo starts where two registered works return the same one. The name keys the grain that runs the work, so
the second never ran and nothing said so. -
Testing:
ExecutionModelTestHost.ResetAsyncworks on a running host. It deleted the checkpoints while the
readers kept their cached positions, so a shared host reset between two tests either never finished
WaitForReadersAsync— the checkpoint said nothing was applied, the store's head said otherwise — or applied the
previous test's facts a second time. It now stops every registered reader, puts it at the store's head and starts
it again, and says how many it moved. The pause that made this possible moved into the store reader every
consumer shares, so a saga's reader stops for a reset as a projection's does. -
Testing: a host whose start fails stops what it started, instead of leaving a half-started silo holding its
ports and timers for the rest of the test run; a start refused because another host took the port is tried again. -
Orleans: a placement filter writes the silo's own metadata entries before it reads them, so a first placement
can no longer judge the silo by a table the runtime has not filled yet. -
Orleans: a resumed command runs where its signed envelope says. The resume routed by the record's own
columns, which the signature never covered: a stored heavy flag flipped in the store sent a signed command past
the heavy pool's bound or outside its aggregate's order, and the signature still verified. The row's identity and
heavy claim are now held to the envelope — a record that disagrees is kept under strict mode and resumed by the
signed claim under permissive mode, logged as117_122— and the HMAC guide says what the signature covers and
what it does not. A command the bus outbox stored during a rolling adoption carries no routing beside its
envelope and is resumed as before. -
Orleans: an aggregate gives up its queue when its silo stops. The queue was given up in the deactivation,
which the runtime starts only once the requests waiting for those commands have ended: the callers waited for
the runtime to give up instead of being told, and the silo's stop waited with them. -
Orleans: a timer's tick no longer deletes the reminder that renewed it. The renewal check and the
unregister now run under the grain's own gate, so a registration for the same purpose and due time from another
turn — a saga re-applying a fact — cannot land between them. -
Orleans: a read model can be rebuilt after its reader or partition count changed. The checkpoint guard
refused every write under another reader's name — including the two verbs that recover from it, a projection's
rebuild and the full replay's reset, which write the beginning under the host's new name. Returning a checkpoint
to the beginning is now a verb of its own (IProjectionCheckpointStore.ResetAsync, with a default implementation
for stores outside the framework) and takes the row over; reading and advancing are guarded as before. -
Orleans: a rebuild or replay whose pause fails leaves no reader paused. The readers were all paused before
the work began, so a pause that failed left the ones that had paused with a pauser nobody released — and since
rebuilds count their pausers, no later rebuild cleared it: the partition read nothing until its silo restarted.
A failed pause now resumes what it paused and fails naming how many it could not pause. -
Orleans:
hybrid: trueon a second store-reading registration is no longer ignored. A host that registered
the projection role and then the saga role withhybrid: truesilently stopped publishing bundles to the bus.
Every registration that asks is now answered: the bus dispatcher is kept, or the registration fails naming what
is missing. A kept dispatcher registered by its type is also owned by the container again, so it is disposed
with its scope. -
Orleans: a paused store reader always comes back, and a rebuilt read model holds every fact. A projection's
rebuild, the full replay's reset and the test host's reset paused the readers with a count that knew none of its
pausers and lived only in the activation. A pauser that died between pause and resume left the readers paused
until their silo restarted, with no stall counted; a resume that was retried released another rebuild's pause;
and a reader resumed before the truncation — early, or in an activation that had moved and forgotten the pause —
applied facts, advanced past them and watched the truncation delete them, leaving the read model short until the
next rebuild. A pause now belongs to its pauser and lasts while the pauser renews it (a lease of sixty seconds,
renewed every twenty); a pause that is not renewed lapses, the reader resumes and logs117_123(warning). A
resume releases only its own pauser's pause however often it is repeated. A rebuild and a replay return the
checkpoints to the beginning again after the read model is emptied — waiting first for any batch a reader still
had in flight — so what a reader applied before is read
again after it — a fact applied in between is applied twice, which a rebuildable projection must tolerate as a
full replay already requires. Upgrade note: during a rolling upgrade, a rebuild or replay started from a
4.1.x silo pauses an upgraded silo's readers for at most ten minutes, because it cannot renew, and resets the
checkpoints only once, and one started from an upgraded silo fails naming a reader still hosted on 4.1.x; rebuild
once every silo runs the new version. -
Orleans: a wake-up that cannot be sent is logged (
117_121, debug) and no longer costs the other consumers
of that commit theirs. -
Orleans: the heavy-work pool runs its units beside each other again. Since the pool became a placed grain
its eight slots lived in one activation, which runs one turn at a time: heavy units ran one after another, and a
handler that computed without awaiting anything blocked the pool's front door, so the next heavy command was not
accepted, not leased, and handed over again after the grace — counting an attempt each time, up to being kept for
an operator although it never ran. A unit now runs on one of the silo's own heavy workers, off the pool's
scheduler; the pool still accepts, leases and de-duplicates the hand-over. -
Orleans: a freed permit goes to a running heavy unit before a waiting one. A running unit whose permit was
lost with its keeper and refused when it registered again asks at every renewal, while units waiting to start ask
every hundred milliseconds — under a queue of heavy work the running unit never counted against the bound again.
The permit table now reserves the room for it, so the bound is exceeded only until a running unit ends. -
Orleans: corrections in the execution model's guides. The response timeout is named
SiloMessagingOptions.ResponseTimeout(ClientMessagingOptions.ResponseTimeoutfrom outside the cluster) —
configuring the sharedMessagingOptionsbase changed nothing; a death declaration needs the lesser of
NumVotesForDeathDeclarationand half the active silos, so a two-silo cluster does declare one; the overlap of
singleton work during a failover is bounded by the table refresh only while the declared silo can still read the
membership table; the cut-over draws the seeded head in one window with nothing appending, and the guide now says
what the alternative costs — applying twice where the bus workers stay up, applying not at all where they stop
while appends continue; and a full re-read is a reset without a seeding, because a checkpoint at the beginning
counts as absent and is seeded again.