Repository navigation
Conversation
Fire, Tick, RunService and DeliverToActor now hold a lock for the whole step, so a host's timer loop no longer loses acknowledged steps. A step that applied to the kernel but wasn't appended leaves the instance ahead of the journal, so the Handle refuses further steps with ErrHandleFailed until it's recovered. Relates to #214. Signed-off-by: Ray Karnes <ray.karnes@stablekernel.com>
…rToActor RunService and DeliverToActor refuse steps after a failed Append and return an ended context's error, but only Fire and Tick were tested. New tests fail an Append under each and end the context before each, and check nothing ran or was journaled. The Tick context test now also checks that nothing was appended. The RunService and DeliverToActor docs said a failed step returns false; they return true with ErrHandleFailed once the service settled or the actor received the event. The docs now say so, name a context that ends during the Store write as a possible cause, and note that the latch relies on the Append running inline. Signed-off-by: Ray Karnes <ray.karnes@stablekernel.com>
Findings from Temple's review of #233Temple reviewed #233, the PR this one replaces. These are his findings and where each one is handled. Line numbers are from #233's old head ( High
Medium
Low
A fresh review round runs on this PR before it changes, covering correctness, concurrency and replay determinism. The PRs above it will be rebased once it merges. |
Review of the rebased change (494c78a..2f8b57c)The change is rebased onto main after #232 merged, plus one fix commit (2f8b57c). All seven dimensions are confirmed, so the PR is ready. The reviewers (AI agents, one per dimension, each starting with a clean context) could only read code: they traced the code paths but couldn't run tests. I ran the tests and the mutation checks myself.
Fixed in 2f8b57c
Rejected: pre-existing on main, fixed higher in the stackThese come from code this PR doesn't change. Fixing them here would mean fixing more than the bottom PR of the stack.
Pre-existing, possibly not tracked yet
Non-blockers left as they are
VerificationOn
|
| if err := h.refuse(); err != nil { | ||
| return nil, err | ||
| } | ||
| // A context that ended in the lock queue must stop here: the scheduler would consume | ||
| // a due timer whose Fire the kernel then refuses. | ||
| if err := ctx.Err(); err != nil { | ||
| return nil, err | ||
| } |
There was a problem hiding this comment.
Should we just fold ctx.Err() into refuse() ?
|
#245 passes the step's context to guards, actions, reducers and actor factories. Today only a service's call back into its own Handle is caught with |
What this change does
Two #214 fixes in the same code path. Rebased onto main now that #232 has merged. This replaces #233, which GitHub couldn't reopen after the rebase.
Fire,Tick,RunServiceandDeliverToActorhold a lock for the whole step, so they're safe from different goroutines. This is the contract in my answer to question 10 on #214, which is a working answer, not a ruling.Append. A step that applied to the kernel but may not have been recorded fails the Handle. That call and every later one return the newErrHandleFailed, wrapping the cause, until a new Handle is recovered.nextStepnow advances right after the append, so a failed checkpoint no longer makes the next step conflict.Tick,RunServiceandDeliverToActorreturnctx.Err()once they hold the lock, so a deadline that ended in the lock queue changes nothing. Before,Tickconsumed a due timer the kernel then refused.Please check these:
Appendstays inline, on the session's single owner goroutine. If it moves off that goroutine, the lock scope and this failure handling change.state.ServiceRunnerruns and settles in one call, so I couldn't narrow it here.Handledoc says so.Instance()returns the live instance outside the lock.ErrFenced, a newer owner holds the instance, soRecoverwon't help. The docs say to stop.Append, a service whose result wasn't recorded is in flight again on the recovered Handle, soRunServiceruns it a second time. AnInvokeActoractor restarts from its initial state, and aSpawnactor doesn't return.Known and left open:
TickorDeliverToActorcan still journal a step the kernel refused, and a canceled delivery can makeRecoverfail. An already-ended context no longer reaches that path.Storethat panics mid-step skips the failure latch, if the host recovers the panic.Runner.Firecalls on one id aren't serialized. The doc says so.Fireleaves a Trace only in the live instance, so a later snapshot can differ byte for byte.Why
A host's timer loop racing
Firelost acknowledged steps, and a failedAppendleft the live instance ahead of the journal.Relates to: #214, #245
Evidence
On
GOTOOLCHAIN=go1.27.3, indurable:go test ./...TestRunner_FireDuringTimerLoop_LosesNoAcknowledgedSteps,TestRunner_FireAfterFailedAppend_MatchesJournal, and the failure-latch and ended-context tests forRunServiceandDeliverToActorinhandle_failure_test.go, each checked to fail with its guard removed. The timer-loop test now also fails if a Fire or Tick errors or no timer fires. It's reliable under-race; a plain run can pass by luckgo test -race ./...golangci-lintChecklist
git commit -s) per the DCOtype: subject)mage checkpasses locally (lint + race tests + govulncheck): not run as a whole, and it cannot pass until the rest of the chain lands