Skip to content

D237 — A bounded fetch, and a read backoff, for a remote that does not answer

(Superseded in part by D311: a write whose fetch fails now commits on the local base when HEAD has nothing unpushed, instead of failing.)

Decision. gitx.Fetch runs under a 15s deadline and kills git's whole process group when it expires, returning a RemoteTimeoutError that names the remote. After a failed fetch, read-only tools skip SyncIn for 60s and serve the local clone; the check is repeated under the git lock, so calls queued behind the failing fetch do not each start their own. Writes are unchanged: they fetch, and fail, every time. The client words a timeout while awaiting headers as "the server accepted the request but did not answer in time", not "could not reach".

Why. A remote whose host drops packets held the fetch for the OS TCP connect timeout (~75s on macOS) while the client gives up at 30s, so every sync failed against a KB the server would in the end have served from its clean local clone, and retries queued on the git lock one full wait each (#348). The fetch is the operation that meets the unreachable remote first; pull and push run only after a fetch has just succeeded, so they are left without a deadline rather than risk killing a rebase halfway. The process group matters because git fetch over http runs git-remote-http as a child: killing only git left one orphan per timeout. The cost is that a genuinely slow fetch over 15s now fails, and a read in the 60s after a failure may miss a commit that has since become fetchable.

Alternatives rejected. - Raise the client timeout to cover the OS connect timeout: every call on a healthy server with a broken remote would still take 75s, and retries still compound. - Skip the fetch when the local clone is clean and even with origin/<branch>: "even" is only known after a fetch, which is the call that hangs. - A server-wide WriteTimeout: D166 already rejected it, since it truncates a slow success instead of bounding the operation. - A backoff for writes too: a write must not commit on a base it could not refresh; failing it is the honest answer.

Consequences. Reads against a KB whose remote is down answer within one fetch deadline, then immediately for 60s. FetchTimeout is a package variable only so tests can shorten it; it is not configuration. A future git network operation that can hang (pull, push, ls-remote) should use the same killGroupOnCancel shape rather than a bare CommandContext.