Skip to content

Throttle initial Git repository.status() across repositories#318862

Merged
dmitrivMS merged 1 commit into
mainfrom
dev/dmitriv/throttle-git-initial-status
May 30, 2026
Merged

Throttle initial Git repository.status() across repositories#318862
dmitrivMS merged 1 commit into
mainfrom
dev/dmitriv/throttle-git-initial-status

Conversation

@dmitrivMS

Copy link
Copy Markdown
Contributor

Fixes #318279
Fixes #318764

Problem

In workspaces with many git repositories (~50+ folders in a multi-root .code-workspace), the extension host gets killed ~25–30s after startup, auto-restarts, and crashes in a loop. The reporter's exthost.log shows thousands of [Decorations] CAPPING events from decorations provider vscode.git warnings just before each kill, and the cadence matches the IPC unresponsive watcher firing after missed pings. git.enabled: false is a known workaround.

Root cause

Model.openRepository() ends with a fire-and-forget repository.status() for every repo discovered during the initial scan. Each _updateModelState() fans out ~8 git subprocesses (HEAD, remotes, submodules, worktrees, getStatus, getRefs, ...) and then parses the output on the ext-host's single thread. With N repos opening through Promise.all(... openRepository ...) in doInitialScan, we get ~N×8 in-flight git subprocesses and a large blocking parse burst. For N≈50 that's enough to starve IPC pings and trip the unresponsive-host kill.

The decoration CAPPING warnings are a symptom of the same overload (each completed status fires a large decoration delta), not the cause.

Fix

Throttle the initial fire-and-forget repository.status() calls across repositories via a model-level Limiter<void>(5). The repo is still registered with SCM immediately (this.open(repository) runs unchanged); only the resource/decoration population is throttled, which was already async.

Concurrency 5 matches existing uses in extensions/git/src/repository.ts and extensions/git/src/git.ts — enough parallelism to keep cores busy without saturating the host.

Applies to runtime opens too (workspace-folder additions, eventuallyScanPossibleGitRepositories), so the same crash can't be reproduced by adding folders incrementally.

Verification

  • Typecheck passes (tsc -p extensions/git --noEmit).
  • No existing tests depend on unbounded parallelism (smoke test waits on an event).
  • Manual repro on the reporter's workspace would be ideal — flagging @lszomoru.

Not in scope

  • The redundant git rev-parse --show-toplevel calls for subfolders of already-opened repos (previously explored in Avoid unncessary git rev parse calls #313856) would further cut process count but don't change the crash dynamics; better as a follow-up so each change can be evaluated independently.

Each repository.status() spawns ~10 git subprocesses. With many repos in a workspace, fan-out can saturate the extension host and trigger the IPC unresponsive watcher kill loop. Bound concurrency with a Limiter(5).

Fixes #318279

Fixes #318764
Copilot AI review requested due to automatic review settings May 28, 2026 22:37
@dmitrivMS dmitrivMS self-assigned this May 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Throttles the initial fire-and-forget repository.status() calls in the git extension's Model.openRepository() through a model-level Limiter<void>(5), preventing the extension host from being killed when opening large multi-root workspaces (~50+ git repos) where each status fans out ~8 git subprocesses simultaneously.

Changes:

  • Import Limiter from ./util.
  • Add a private _initialStatusLimiter = new Limiter<void>(5) field on Model.
  • Route the initial repository.status() call through the limiter in openRepository.
Show a summary per file
File Description
extensions/git/src/model.ts Adds a 5-way concurrency limiter around initial repository.status() calls so SCM registration still happens immediately, but the per-repo subprocess/parse burst is bounded.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 0

@dmitrivMS

Copy link
Copy Markdown
Contributor Author

@dmitrivMS dmitrivMS changed the title Git - throttle initial repository.status() across repositories Throttle initial Git repository.status() across repositories May 28, 2026
@dmitrivMS
dmitrivMS marked this pull request as ready for review May 29, 2026 22:10
@dmitrivMS

Copy link
Copy Markdown
Contributor Author

Tested with 30 repos and on Windows, it looks ok, but also asked customer to confirm if the crash is gone in their scenario.

@dmitrivMS
dmitrivMS merged commit f45c4ba into main May 30, 2026
37 checks passed
@dmitrivMS
dmitrivMS deleted the dev/dmitriv/throttle-git-initial-status branch May 30, 2026 01:23
@vs-code-engineering vs-code-engineering Bot added this to the 1.123.0 milestone May 30, 2026
@dmitrivMS dmitrivMS added git GIT issues perf labels May 30, 2026
@vs-code-engineering vs-code-engineering Bot locked and limited conversation to collaborators Jul 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

git GIT issues perf

Projects

None yet

3 participants