Skip to content

Keep a coalesced clone or fetch running when the request that started it disconnects [patch] - #81

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/44-clone-outlives-request
Oct 8, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/44-clone-outlives-request

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #44

What was wrong

MirrorFetcher.EnsureCurrentAsync ran the leader's clone or fetch under the leader request's own RequestAborted token. When that client hung up, GitRunner killed git, the leader's ticket was disposed without Complete(true), and every follower was released as failed. With no mirror on disk yet, followers got Unavailable (503 no-mirror). A repository whose clone takes longer than the client timeout was never mirrored.

Change

  • The clone or fetch now runs in LeadAsync, under IHostApplicationLifetime.ApplicationStopping, and owns the leader's ticket. It reports the outcome to followers whether or not the leader's client is still connected.
  • The leader awaits that work with .WaitAsync(requestToken). A disconnecting client stops waiting, but the work keeps running.
  • Followers are unchanged. They already wait with their own token and FetchTimeout. Disposing the follower ticket moved into FollowAsync so ownership is explicit (this also satisfies CA2025).
  • FetchTimeout still bounds the git process through GitInvocation.Timeout in GitRunner. Host shutdown still cancels it.
  • MirrorFetcher gains an IHostApplicationLifetime constructor parameter. DI already provides it under the ASP.NET Core host, and the two test builders pass a new FakeHostApplicationLifetime.

Tests

New MirrorFetcherCoalescingTests:

  • EnsureCurrent_WhenTheLeaderDisconnectsMidClone_FinishesTheCloneForTheFollowerAsync: the leader's token is cancelled mid-clone while a follower waits. The leader gets OperationCanceledException, the clone completes, the follower gets Current, and there is exactly one clone. This is the issue's first acceptance criterion.
  • EnsureCurrent_WhenTheHostStopsMidClone_CancelsTheCloneAsync: StopApplication() mid-clone cancels the git invocation. This is the second criterion. Kill-on-cancel is already pinned by GitRunnerTests.

I reverted the fix locally and ran both tests: both fail. Restored, both pass. Full suite: 243/243 pass on .NET 10.0.401 (Linux).

🤖 Generated with Claude Code

https://claude.ai/code/session_01FM7p8BsZu36YCjPsutDXQN


Generated by Claude Code

… it disconnects [patch]

The leader ran clone and fetch under its own request's RequestAborted token, so a client
hanging up killed git and released every follower as failed. A clone longer than the
client timeout therefore never finished, and followers got 503 no-mirror.

The work now runs under IHostApplicationLifetime.ApplicationStopping and owns the
leader's ticket, while each caller awaits it with its own token. FetchTimeout still
bounds it through GitRunner, and host shutdown still cancels it.

Fixes #44

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FM7p8BsZu36YCjPsutDXQN
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 197b988 into main Oct 8, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/44-clone-outlives-request branch October 8, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants