Files
tty7/src
l0ng-aiandl0ng-ai af3928da1e fix(tree-sync): pay back a remote window's owed tree pull (#472)
* fix(tree-sync): pay back a remote window's owed tree pull

A window opening onto a remote workspace is empty until `hydrate` pulls
the machine's tree and rebuilds its tabs from it, and it has to be: an
empty window diffs into "close every tab", so `sync_window` holds
anything back until the pull lands. When the pull fails,
`owe_rehydration` records the debt and returns, on the promise in its own
doc comment that the next sync settles it — "which is what a reconnect
does through `on_link_up`".

`on_link_up` is called for `HostId::LOCAL` and nowhere else. On a remote
host the debt was only ever settled by a reconnect completing, by an edit
in the window, or by restarting the app. So a pull that failed while the
link stayed up was never noticed again: no reconnect, and an empty window
has nothing in it to edit. The window sat on the home page with every tab
and every shell still on the machine, and only a restart brought them
back.

Two ways to fail a pull with a healthy link, both routine. A `MachineGet`
can overrun its ten seconds on a slow link. And a `WorkspaceCreate` can
lose its race with `start_prime`, which runs the same create from the
other side of the same window opening — that one fires on every remote
workspace opened, and is only invisible because the workspace it usually
lands on is empty anyway.

So: arm a backed-off retry when the debt is taken on, drive it through
`sync_window` where the rules about whether a window may still adopt the
machine's layout already live, and stop treating a lost create as a
failure — read the tree again and hydrate from what is really there.
`on_link_up` is also wired to a remote link coming up, which is what the
comment always claimed: a link the switcher connects finishes no attempt,
so nothing told its windows the machine could be reached.

* fix(tree-sync): end the backoff with the run of failures, and stop shouting

Review follow-ups on the owed-pull retry.

The attempt count paces the retry, so it has to mean "failures in a row",
but it was only cleared when a hydration landed. A debt abandoned rather
than paid — a `Replace` dropped because the user filled the window in
themselves — and a prime that landed both left it standing, so the next
first failure waited the 30s cap on an outage that was already over. It
is now cleared wherever the run ends.

A window left open on a machine that is really gone retries forever by
design, which meant a warn and an info every ~45s for as long as it stayed
open. Once the backoff settles at its cap those lines stop being events
and become a fact about the machine, so they step down to debug. The retry
is exactly as persistent; only the volume drops.

Also: report the create's own refusal when the reread finds the workspace
still missing, and say at debug that the reread happened at all — the race
recovery was silent, so the extra round trip was invisible when reading a
log. And correct the comment on the window-gone guard: closing a window
drops its whole `WsState` through `forget`, debt and all, so nothing is
parked for the next opener.

Tests: the count ends with the run at all three sites, the level steps
down at the settle point, and the armed retry is driven through a real
timer (advance_clock) into the window-gone guard — the first coverage of
the retry actually firing rather than of the predicate it consults.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-10 23:47:29 +08:00
..