Bound runtime credential fetch so prefetch can't hang on auth#2046
Draft
tyrielv wants to merge 1 commit into
Draft
Bound runtime credential fetch so prefetch can't hang on auth#2046tyrielv wants to merge 1 commit into
tyrielv wants to merge 1 commit into
Conversation
6223f5a to
12eb591
Compare
The runtime credential path (HttpRequestor.SendRequest -> GitAuthentication.TryGetCredentials/RejectCredentials -> TryCallGitCredential) called git-credential with timeoutMs = -1, so Process.WaitForExit(-1) waited forever. When a GCM auth popup was missed (e.g. behind another window), the mount's background maintenance PrefetchStep blocked indefinitely while holding the shared prefetch-commits-trees.lock, which in turn blocked a user-initiated `gvfs prefetch`. The mount startup auth path was already bounded via credentialTimeoutMs; this extends the same bound to the runtime path: - TryGetCredentials takes credentialTimeoutMs (default DefaultCredentialTimeoutMs) and plumbs it to TryCallGitCredential. - RejectCredentials, which reloads the credential on the 401-retry leg, takes and plumbs the same timeout (this leg is the actual stale-token hang path and was otherwise still unbounded). - HttpRequestor exposes a protected virtual CredentialTimeoutMs and passes it to both TryGetCredentials and RejectCredentials. The runtime bound uses the generous BackgroundCredentialTimeoutMs (120s) rather than the 30s default: the mount's requestor is shared by the background maintenance prefetch, interactive on-demand hydration, and the user-initiated prefetch/clone verbs, where a human may legitimately take longer than 30s to answer a GCM cold-start / MFA / smartcard prompt. 120s still bounds the hang while being long enough not to cut off a prompt the user is actively answering. On timeout the fetch fails, backoff engages, the download gives up, and the lock is released instead of hanging forever. Adds unit tests covering the timeout and explicit-timeout success paths. Fixes AB#63011829 Assisted-by: Claude Opus 4.8 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
12eb591 to
2732ff8
Compare
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.
Problem
A user missed a Git Credential Manager auth popup (it was behind another window). Instead of timing out, GVFS waited indefinitely. The blocked operation was the mount's background maintenance
PrefetchStep(not user-initiated), which holds the sharedprefetch-commits-trees.lockon the gvfs object cache. Because that lock was never released, a subsequent user-initiatedgvfs prefetchblocked behind it forever.Root cause
Runtime credential fetches flow through:
The mount startup auth path was already bounded (
credentialTimeoutMsonTryInitializeAndQueryGVFSConfig), but the runtimeTryGetCredentialspath — used by every object/pack download, including the background maintenance prefetch — was never given a finite timeout. The timeout plumbing already exists end-to-end inGitProcess/InvokeGitImpl; the runtime path simply never passed a finite value.The mount's maintenance prefetch and on-demand hydration share one
GitObjectsHttpRequestor/ oneGitAuthentication, so bounding the shared runtime path fixes the maintenance-prefetch hang.Fix
Bound the runtime credential fetch so it can never wait forever:
GitAuthentication.TryGetCredentialsgains a trailingint credentialTimeoutMs = DefaultCredentialTimeoutMsand passes it toTryCallGitCredential.GitAuthentication.RejectCredentials— which reloads the credential on the 401-retry leg (SendRequestcalls it before re-enteringTryGetCredentials) — takes and plumbs the same timeout. This leg is the actual stale-token hang path and was otherwise still unbounded.HttpRequestoradds aprotected virtual int CredentialTimeoutMs => GitAuthentication.BackgroundCredentialTimeoutMs(120s) and passes it to bothTryGetCredentialsandRejectCredentialsinSendRequest.Why 120s and not 30s: the mount's requestor is shared by the background maintenance prefetch, interactive on-demand hydration, and the user-initiated
gvfs prefetch/clone verbs. A 30s bound risks converting today's hang into a spurious auth failure when a human is legitimately slow to answer a GCM cold-start / MFA / smartcard prompt. 120s (the existing, already-vettedBackgroundCredentialTimeoutMs) still bounds the indefinite hang — the reported case was a prompt never answered — while giving a noticed prompt ample time.On timeout,
TryGetCredentialsreturns false and engages backoff; after the second failed attemptIsBackingOffshort-circuits further attempts. The download gives up,TryDownloadPrefetchPacksreturns false, andPrefetchStepreleasesprefetch-commits-trees.lock— unblocking the user-initiated prefetch — instead of hanging forever.All existing callers compile unchanged (new parameter has a default; property is virtual with a default).
Tests
TryGetCredentialsTimesOutWhenCredentialManagerDoesNotRespond— simulates agit credential filltimeout and assertsTryGetCredentialsreturns false with a "did not respond" error.TryGetCredentialsSucceedsWithExplicitTimeout— happy path with an explicit finite timeout.GitAuthenticationTestsclass: 22/22 passing;GVFS.UnitTestsbuilds with 0 warnings / 0 errors.Fixes AB#63011829