perf(worktree): parallelize head identity metadata reads (#17449)

This commit is contained in:
Neil
2026-08-30 23:03:55 -07:00
committed by GitHub
parent 3e2d0f2118
commit 30ca6bc953
2 changed files with 87 additions and 20 deletions
@@ -0,0 +1,54 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { readFileMock, readdirMock, concurrency } = vi.hoisted(() => ({
readFileMock: vi.fn(),
readdirMock: vi.fn(),
concurrency: { active: 0, max: 0 }
}))
vi.mock('node:fs/promises', () => ({
readFile: readFileMock,
readdir: readdirMock
}))
import { readGitCommonHeadIdentities } from './worktree-head-identity-reader'
const WORKTREE_COUNT = 16
describe('readGitCommonHeadIdentities concurrency', () => {
beforeEach(() => {
readFileMock.mockReset()
readdirMock.mockReset()
concurrency.active = 0
concurrency.max = 0
readdirMock.mockResolvedValue(
Array.from({ length: WORKTREE_COUNT }, (_, index) => ({
name: `wt-${index}`,
isDirectory: () => true
}))
)
readFileMock.mockImplementation(async (filePath: string) => {
concurrency.active += 1
concurrency.max = Math.max(concurrency.max, concurrency.active)
await new Promise((resolve) => setTimeout(resolve, 5))
concurrency.active -= 1
if (filePath.endsWith('/gitdir')) {
return `/workspace/${filePath.match(/wt-\d+/)?.[0] ?? 'wt'}/.git\n`
}
return `${'a'.repeat(40)}\n`
})
})
it('overlaps linked-worktree metadata reads while preserving listing order', async () => {
const identities = await readGitCommonHeadIdentities('/repo/common')
expect(identities).toHaveLength(WORKTREE_COUNT)
expect(identities.map((identity) => identity.worktreePath)).toEqual(
Array.from({ length: WORKTREE_COUNT }, (_, index) => `/workspace/wt-${index}`)
)
// The bounded worker pool should overlap independent reads without launching
// an unbounded promise fan-out.
expect(concurrency.max).toBeGreaterThan(1)
expect(concurrency.max).toBeLessThanOrEqual(8)
})
})
+33 -20
View File
@@ -1,12 +1,18 @@
import { readdir, readFile } from 'node:fs/promises'
import type { Dirent } from 'node:fs'
import { basename, dirname, isAbsolute, join } from 'node:path'
import type { WorktreeHeadIdentity } from '../../shared/worktree/types'
import { mapWithConcurrency } from '../../shared/map-with-concurrency'
// Why: the whole point of this reader is replacing `git worktree list` fanout
// with bounded metadata-file reads, so head freshness never re-creates the
// spawn pressure that stalled terminal input. Keep it spawn-free.
const MAX_SYMREF_DEPTH = 5
// Head identity refreshes run on every git-common poll. Keep metadata reads
// bounded while avoiding a serial round trip per linked worktree (especially
// noticeable on WSL/UNC and network-backed worktrees).
const HEAD_IDENTITY_READ_CONCURRENCY = 8
async function readTrimmedFile(path: string): Promise<string | null> {
try {
@@ -127,32 +133,39 @@ export async function readGitCommonHeadIdentities(
}
}
let entries
let entries: Dirent[]
try {
entries = await readdir(join(commonDirPath, 'worktrees'), { withFileTypes: true })
} catch {
return identities
}
for (const entry of entries) {
if (!entry.isDirectory()) {
continue
const linkedEntries = entries.filter((entry) => entry.isDirectory())
// mapWithConcurrency retains input order, so publishing identities stays
// deterministic while independent worktree metadata reads overlap.
const linkedIdentities = await mapWithConcurrency(
linkedEntries,
HEAD_IDENTITY_READ_CONCURRENCY,
async (entry) => {
const entryPath = join(commonDirPath, 'worktrees', entry.name)
const gitdirContent = await readTrimmedFile(join(entryPath, 'gitdir'))
if (!gitdirContent) {
return null
}
// `gitdir` holds `<worktree>/.git`, absolute or (with relative-path
// worktrees) relative to the entry dir.
const gitdirAbsolute = isAbsolute(gitdirContent)
? gitdirContent
: join(entryPath, gitdirContent)
return readHeadIdentity(
commonDirPath,
join(entryPath, 'HEAD'),
dirname(gitdirAbsolute),
packedRefs
)
}
const entryPath = join(commonDirPath, 'worktrees', entry.name)
const gitdirContent = await readTrimmedFile(join(entryPath, 'gitdir'))
if (!gitdirContent) {
continue
}
// `gitdir` holds `<worktree>/.git`, absolute or (with relative-path
// worktrees) relative to the entry dir.
const gitdirAbsolute = isAbsolute(gitdirContent)
? gitdirContent
: join(entryPath, gitdirContent)
const identity = await readHeadIdentity(
commonDirPath,
join(entryPath, 'HEAD'),
dirname(gitdirAbsolute),
packedRefs
)
)
for (const identity of linkedIdentities) {
if (identity) {
identities.push(identity)
}