Repository navigation
perf(enginehost): a retiring host gives the workspace up before it saves - #1972
Merged
Merged
Conversation
Two failing tests. A host on its way out keeps the workspace lock while its conversations save, so no new host can start; and a launch that finds the lock busy with nothing listening waits out the whole birth wait without starting a host when the holder lets go.
A host on its way out closed its conversations, which waits for each one's last save, and only then removed its socket and released the workspace lock. In a large folder that save runs for its whole 30 s bound. A launch arriving in that window found the lock busy and no socket, waited the full 10 s birth wait for a host nobody was starting, and then opened its conversation in its own process: about 15 s to the first frame. The host now removes its socket and releases the lock first, then saves. Each conversation keeps its own journal lock until its save is done. And a launch that finds the lock busy now asks again on every turn of its wait, and starts the host itself the moment the holder lets go, which covers hosts of older builds that still save while holding the lock.
The full suite caught it: an environment restart retires the idle host and the new host reopens its journals, which the old process was still closing. A retiring host now holds host.saving from before it gives up the workspace until its last conversation is closed, and waitForGone waits up to one second for it once the slot is free. Launches that only need the slot never wait on it, and a slow save past that second no longer holds anybody. Also the change entry.
AbirAbbas
added a commit
that referenced
this pull request
Oct 26, 2026
…urrow v0.3.4 Brings the workspace-ownership branch up to dev cf282dd, which adds the Raft relay (#1950), the two-computer fixes (#1967), #1972, #1974 and #1976. - directory record types are now clusterstate aliases, so frame hints move into the replicated state machine: FrameHint and the frame_hints field on Cell, CellInit and Publish, bounded deterministically in the shared rules, dropped from listings and kept on single-cell reads. Signed v1 publishes with hints are accepted; unknown fields are still refused; existing canonical bytes are unchanged and no schema or protocol version moves. - every take path records the send lease, including the workspace-set and identity-adopting restores; an already-held chat keeps its hold when the record fails. - a removed computer cannot take through the folder-mapping road either, and unsent words and the pending send survive the root chooser. - the seal refuses a folder holding home before taking the checkpoint lock. - furrow pin follows dev at v0.3.4, which contains v0.3.2's non-mutating SQLite capture. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A launch after the dev channel replaced the binary took about 15 s. The old host had retired itself, but it held the workspace lock while it saved its conversations, and it had already closed its socket. The new launch saw the lock held and assumed a host was starting. It waited the full 10 s birth wait, then opened the conversation in its own process.
Each conversation still holds its own journal lock until its save finishes, so a new host cannot open a conversation that is still being written. The second change covers hosts from older builds, which still save while holding the lock.
A retiring host also holds a new
host.savinglock from before it gives the slot up until its last conversation is closed.RetireandStop(waitForGone) wait up to 1 s more on that lock once the slot is free, so an ordinary save finishes before the new host reopens those journals. The launch path never waits on it. The full suite caught this need:TestDifferentEnvironmentRestartsIdleRealHostAndReopensJournalsfailed without it.Evidence
Tests count states and order, not time (
internal/enginehost/handover_test.go):TestARetiringHostLetsGoOfTheWorkspaceBeforeItsSaveFinishes: the host retires while a conversation's Close is blocked. The test checks that the lock is free, that a second host can start, and that the first host's late exit leaves the new socket in place.Before:
the host kept the workspace lock while its conversation was still saving. After: pass.TestRetireWaitsForTheSaveOfTheHostItAskedToGo: while the save is still running, the slot is free and Retire has not returned. Retire returns after the save.TestALaunchStartsTheHostOnceAHolderOnItsWayOutLetsGo: the lock is held with no socket, Attach starts waiting, then the lock is released. The test checks that exactly one host is spawned and Attach connects to it.Before:
engine host: no host answered(after 10 s). After: pass.Rig on Spark (copied codeaf home, fresh 240k-file folder so the first save is slow): open the folder, quit,
engine --stop, then launch again 1 s later.engine --stopIn the dev run, the client's goroutine dumps show
enginehost.waitForHost < Attach < localLink.dial < remote.Roamfrom 0.2 s to 10 s. The old host logged the same line as the laptop did:could not finish saving this chat: still saving this chat on this computer: context deadline exceeded.Full suite:
make check(LC_ALL=C) on Spark, fleet job20261026-192124-002488, commit d4db04e. Every enginehost test passes, plus-race -count=20on the new tests and the environment-restart test. Failures:internal/release(2),internal/seniordev/util TestFindUpRootFirst.cmd/codeaf TestRunInCellSealsItsCallsAndContinuesFromThem(TempDir cleanup) andinternal/syncsetup TestR7PersonalLeaderFallbackAndNoAutomaticRegain.Merge Danger
Door: two-way
Blast Radius: host handover
RetireandStopnow return when the workspace is free and its saves are done, or 1 s after the slot frees, whichever comes first. Before, they waited up to 10 s and then failed. A launch that comes during a slow save and asks for the conversation still being written gets the existing "open in another window" landing: a new conversation with the held row armed. Before, it waited up to 10 s and then opened the conversation in its own process.🤖 Generated with Claude Code