mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
perf(worktree): parallelize head identity metadata reads (#17449)
This commit is contained in:
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user