Restore finished remote loops a host reboot killed - #501
Merged
Merged
Conversation
Signed-off-by: scgopi <scgopireddy@gmail.com>
A finished goal or time loop whose pane was open when its codespace restarted dialed 'waiting for graphcoded' forever: the pane leaves every unattended loop to the daemon, and the remote sweep skipped every resolved node. The sweep now probes each host once for finished loops whose session is missing and was last seen in an earlier boot, and brings those back as their banked conversation, with no task, poller, heartbeat or state change. The daemon's ensure records the boot marker too, and a daemon kill clears it, so a session ended on purpose is never restored. Signed-off-by: scgopi <scgopireddy@gmail.com>
The reboot probe ran on every liveness sweep once a project had a finished loop, so an idle codespace went from no dials to one gh run a minute. A remote pane now touches a per-host stamp before each redial, and the sweep probes a host only when a stamp is newer than its last answered probe: a healthy host costs nothing, and an outage is bounded by the pane's own Signed-off-by: scgopi <scgopireddy@gmail.com> #480 schedule.
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
After a codespace or remote-host restart, a finished unattended loop (goal or time, state succeeded/failed/stalled/stopped) whose pane was still open dialed
reboot wait-daemonindefinitely. It dialed every ~19s before 0.1.76, and since 0.1.76 it pauses after the #480 schedule, but it never came back on its own. Sibling loops on the same codespace recovered fine.Root cause
GhosttyTerminalView+Remote.swift:132-181). For an unattended loop it never launches: it logsreboot wait-daemon, exits 255 and waits forgraphcoded.GraphStore.ensureUnattendedSessionsAlive, which ensuredrunsUnattended && !isResolvedonly.isResolvedis succeeded/failed/stalled/stopped (LoopNode.swift:574).openNodesends.resumeSessionfor a resolved, non-stopped loop (AppFeature.swift:746-752). Everyensure resumein the log follows that session'sconnect wait-daemonwithin 0-13s. The sweep produced no ensure at all in two days.SSHReconnectLoop.codespaceScript). It does not change who restores the session. No other uncapped codespace dial path remains in the pane:codespaceScriptretries on exit 1 and 255 under the schedule, and every other exit passes through.Fix: one owner per state
restoreScriptZmxSessionLauncher.restoreRebootedRemote. That dials only when a pane of the host has redialed since the host's last answered probe. Each remote pane touches a per-host stamp (redialStamp) before every redial, and on a healthy host no pane redials, so the sweep spends nothing. Then one probe per host names the sessions that are missing and whose boot marker is from an earlier boot. Only those get a create dial, behind the same boot gate. A failed probe leaves the redial pending, so the host is probed once it is back even if every pane has paused.GraphStore.rebootRestoreCopy: the banked conversation via each backend's resume argv, with no prompt. If nothing is banked, it is a fresh session that opens on a short note, not the loop's task. It does not re-arm goal pollers or heartbeats, and it does not write node state.remoteKillInvocationremoves the marker, so a session the daemon ended on purpose (a finished loop freed, a stop, a delete) is never restored, and its pane reads "ended".RemoteEnsureGate.SessionStart.Test plan
RED: xcodebuild -scheme graphcode build-for-testing on aebe3f5 (tests only) -> exit 65, RemoteSessionResumeTests does not compile: no onRestoreRebootedSessions, rebootRestoreCopy, rebootProbeScript, parseRebootProbe, onlyAfterReboot; the idle-cost tests (aHealthyHostIsNeverProbed, aPaneStampsItsHostBeforeEveryRedial) do not compile on 8a3b5ad: no RebootProbeGate, no redialStamp
GREEN: xcodebuild -scheme graphcode test -only-testing RemoteSessionResumeTests, RemoteRebootRestoreTests, CodespaceDialScheduleTests, DialLogTests -> 48 tests in 4 suites passed, exit 0
REGRESSION: xcodebuild -scheme graphcode test (full) -> 1997 tests in 213 suites passed, exit 0; make check -> exit 0; graphcoded and graphcode-cli schemes build -> exit 0
Loopback rig: user-mode sshd with a fake HOME and a private ZMX_DIR, an isolated
graphcoded, and one finished and one running goal loop. The reboot is simulated by killing both sessions and writing a stale boot marker. The pane side is the reconnect decision fromGhosttyTerminalView+Remote.swiftrun over ssh.reboot wait-daemonover 3.5 min and three sweeps, never ensuredensure fresh→reconnect attach-livereboot wait-daemon, thenensure resumeat the first sweep →reconnect attach-live; argv--resume transcript-of-finished, no goal text, state stillsucceededensure fresh→reconnect attach-liveOn this branch, later sweeps logged nothing further: one agent launch per loop, one zmx session per loop.
Idle cost (codespace, 3 finished loops, no pane redialing)
One healthy
gh codespace sshspawn is 1 call from thecodespacesbucket (GH_DEBUG=api, gh 2.87.0). The dial counts below were measured on the loopback rig as ssh sessions. The per-hour codespace figure is a model: dials multiplied by 1 call.Reboot on the revision, 3 finished loops with their sessions killed and markers from an earlier boot, store loaded:
succeededWith nothing banked, the fresh launch opens on the daemon's usual wake-memory pointer and the finished-loop note. This is the same shape the existing open-a-finished-loop path produces (
resumeResolvedSession), never the loop's task.During an outage, probes are bounded to one per sweep while panes redial, and panes stop on #480's schedule (4 min, then a pause). After that there is one restore dial per rebooted finished loop.
Known limits
Checklist
git commit -s) per the DCOmake checkpass locally