perf(main): retire stale worktree marker probes (#13437)

This commit is contained in:
Neil
2026-08-09 22:24:33 -07:00
committed by GitHub
parent f2b2ece831
commit 8859e73980
3 changed files with 206 additions and 15 deletions
@@ -0,0 +1,117 @@
#!/usr/bin/env node
// Run with: node config/scripts/worktree-base-pending-marker-benchmark.mjs
import fs from 'node:fs'
import { mkdir, mkdtemp, rm } from 'node:fs/promises'
import { syncBuiltinESMExports } from 'node:module'
import os from 'node:os'
import { join } from 'node:path'
import { performance } from 'node:perf_hooks'
import { createJiti } from 'jiti'
const CANDIDATE_COUNT = Number(process.env.ORCA_PENDING_MARKER_BENCH_CANDIDATES ?? '64')
const TOTAL_TICKS = Number(process.env.ORCA_PENDING_MARKER_BENCH_TICKS ?? '900')
const STEADY_TICKS = Number(process.env.ORCA_PENDING_MARKER_BENCH_STEADY_TICKS ?? '300')
for (const [name, value] of [
['ORCA_PENDING_MARKER_BENCH_CANDIDATES', CANDIDATE_COUNT],
['ORCA_PENDING_MARKER_BENCH_TICKS', TOTAL_TICKS],
['ORCA_PENDING_MARKER_BENCH_STEADY_TICKS', STEADY_TICKS]
]) {
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer, received ${value}`)
}
}
if (STEADY_TICKS >= TOTAL_TICKS) {
throw new Error('ORCA_PENDING_MARKER_BENCH_STEADY_TICKS must be smaller than total ticks')
}
const root = await mkdtemp(join(os.tmpdir(), 'orca-pending-marker-bench-'))
const markerPaths = new Set()
for (let index = 0; index < CANDIDATE_COUNT; index += 1) {
const candidate = join(root, `ordinary-folder-${index}`)
await mkdir(candidate)
markerPaths.add(join(candidate, '.git'))
}
const originalStat = fs.promises.stat
let visibleTick = 0
const markerStatTicks = []
fs.promises.stat = async (path, ...args) => {
if (markerPaths.has(String(path))) {
markerStatTicks.push(visibleTick)
}
return originalStat(path, ...args)
}
syncBuiltinESMExports()
let poller
let timeout
try {
const jiti = createJiti(import.meta.url)
const { startWorktreeBaseDirectoryPoller } = await jiti.import(
'../../src/main/ipc/worktree-base-directory-poller.ts'
)
const repo = { repoId: 'repo-1', repoName: 'repo', nestWorkspaces: false }
const target = {
key: `base:local:${root}`,
kind: 'base',
path: root,
repos: new Map([[repo.repoId, repo]])
}
let finish
let fail
const completed = new Promise((resolve, reject) => {
finish = resolve
fail = reject
})
timeout = setTimeout(() => fail(new Error('poller benchmark timed out')), 30_000)
const startedAt = performance.now()
poller = await startWorktreeBaseDirectoryPoller(
target,
() => target.repos,
() => {},
{
pollIntervalMs: 0,
visibility: {
isWindowVisible: () => {
visibleTick += 1
if (visibleTick > TOTAL_TICKS) {
finish()
return false
}
return true
},
onWindowBecameVisible: () => () => {}
}
}
)
await completed
clearTimeout(timeout)
await poller.unsubscribe()
poller = undefined
const steadyStartTick = TOTAL_TICKS - STEADY_TICKS
const steadyMarkerStats = markerStatTicks.filter((tick) => tick > steadyStartTick).length
const totalMarkerStats = markerStatTicks.length
const statsPerCandidateTick = steadyMarkerStats / CANDIDATE_COUNT / STEADY_TICKS
const elapsedMs = performance.now() - startedAt
console.log('Worktree-base marker cooldown benchmark. Lower is better.')
console.log(
JSON.stringify({
candidates: CANDIDATE_COUNT,
totalTicks: TOTAL_TICKS,
steadyTicks: STEADY_TICKS,
totalMarkerStats,
steadyMarkerStats,
statsPerCandidateTick: Number(statsPerCandidateTick.toFixed(4)),
elapsedMs: Number(elapsedMs.toFixed(1))
})
)
} finally {
clearTimeout(timeout)
await poller?.unsubscribe()
fs.promises.stat = originalStat
syncBuiltinESMExports()
await rm(root, { recursive: true, force: true })
}
@@ -1,10 +1,12 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { writeFileSync } from 'node:fs'
import { mkdtemp, mkdir, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
createWorktreePollerWindowVisibility,
startWorktreeBaseDirectoryPoller,
WORKTREE_BASE_BACKSTOP_TICKS,
type WorktreeBasePollEvent,
type WorktreePollerWindowVisibility
} from './worktree-base-directory-poller'
@@ -204,6 +206,65 @@ describe('worktree base directory poller', () => {
expect(fullScans.length).toBeGreaterThan(0)
})
it('retires stale marker probes while preserving backstop detection and path reuse', async () => {
const root = await makeRoot()
const ordinaryFolder = join(root, 'ordinary-folder')
const markerPath = join(ordinaryFolder, '.git')
await mkdir(ordinaryFolder)
const received: WorktreeBasePollEvent[][] = []
const target = makeTarget('base', root)
let fullScans = 0
let pendingMarkerProbes = 0
let writeMarkerOnNextProbe = false
const pendingMarkerMaxTicks = WORKTREE_BASE_BACKSTOP_TICKS * 2
const markerCreationTick = pendingMarkerMaxTicks * 2 + WORKTREE_BASE_BACKSTOP_TICKS * 4
const poller = await startWorktreeBaseDirectoryPoller(
target,
() => target.repos,
(events) => received.push(events),
{
pollIntervalMs: 0,
pendingMarkerMaxTicks,
onFullScan: () => {
fullScans += 1
if (fullScans * WORKTREE_BASE_BACKSTOP_TICKS === markerCreationTick) {
writeFileSync(markerPath, 'gitdir: elsewhere')
}
},
onPendingMarkerProbe: (path) => {
pendingMarkerProbes += 1
if (writeMarkerOnNextProbe && path === markerPath) {
writeMarkerOnNextProbe = false
writeFileSync(markerPath, 'gitdir: elsewhere')
}
}
}
)
cleanups.push(() => poller.unsubscribe())
await waitForEvents(received, (flat) =>
flat.some((event) => event.type === 'create' && event.path === markerPath)
)
await rm(ordinaryFolder, { recursive: true })
await waitForEvents(received, (flat) =>
flat.some((event) => event.type === 'delete' && event.path === ordinaryFolder)
)
writeMarkerOnNextProbe = true
await mkdir(ordinaryFolder)
const events = await waitForEvents(
received,
(flat) =>
flat.filter((event) => event.type === 'create' && event.path === markerPath).length === 2
)
expect(pendingMarkerProbes).toBeLessThanOrEqual(pendingMarkerMaxTicks)
expect(
events.filter((event) => event.type === 'create' && event.path === markerPath)
).toHaveLength(2)
})
it('parks base scans while hidden and losslessly detects changes on resume', async () => {
const root = await makeRoot()
const visibility = createVisibilityHarness()
+28 -15
View File
@@ -63,6 +63,10 @@ export type WorktreeBasePollerOptions = {
onWatchError?: (error: Error) => void
/** Test hook: called whenever a full snapshot scan runs (vs. a gated skip). */
onFullScan?: () => void
/** Test hook: called before a pending `.git` marker stat. */
onPendingMarkerProbe?: (path: string) => void
/** Test hook: overrides the fast-probe window. */
pendingMarkerMaxTicks?: number
}
// Why: these targets used to be recursive FSEvents subscriptions spanning the
@@ -193,7 +197,7 @@ async function startBasePoller(
onEvents: (events: WorktreeBasePollEvent[]) => void,
pollIntervalMs: number,
visibility: WorktreePollerWindowVisibility,
onFullScan?: () => void
options: WorktreeBasePollerOptions
): Promise<WorktreeBaseSubscription> {
let disposed = false
let ticking = false
@@ -202,16 +206,17 @@ async function startBasePoller(
let gateSignatures = await Promise.all(snapshot.gateDirs.map(dirSignature))
let timer: ReturnType<typeof setTimeout> | null = null
let parkedWhileHidden = false
// dir → tick when first seen without a `.git` marker
const pendingMarkers = new Map<string, number>()
const pendingMarkerMaxTicks = options.pendingMarkerMaxTicks ?? PENDING_MARKER_MAX_TICKS
// dir → first probe tick; null means backstop scans only
const markerProbeStartedAt = new Map<string, number | null>()
for (const [dir, marker] of snapshot.markers) {
if (!marker) {
pendingMarkers.set(dir, 0)
markerProbeStartedAt.set(dir, 0)
}
}
const fullScan = async (): Promise<void> => {
onFullScan?.()
options.onFullScan?.()
const next = await snapshotBase(target.path, getRepos())
const nextSignatures = await Promise.all(next.gateDirs.map(dirSignature))
if (disposed) {
@@ -220,14 +225,14 @@ async function startBasePoller(
const events = diffBase(snapshot, next)
for (const [dir, marker] of next.markers) {
if (marker) {
pendingMarkers.delete(dir)
} else if (!pendingMarkers.has(dir)) {
pendingMarkers.set(dir, tickCount)
markerProbeStartedAt.delete(dir)
} else if (!markerProbeStartedAt.has(dir)) {
markerProbeStartedAt.set(dir, tickCount)
}
}
for (const [dir, firstSeenTick] of pendingMarkers) {
if (!next.markers.has(dir) || tickCount - firstSeenTick > PENDING_MARKER_MAX_TICKS) {
pendingMarkers.delete(dir)
for (const dir of markerProbeStartedAt.keys()) {
if (!next.markers.has(dir)) {
markerProbeStartedAt.delete(dir)
}
}
snapshot = next
@@ -239,9 +244,17 @@ async function startBasePoller(
const checkPendingMarkers = async (): Promise<void> => {
const events: WorktreeBasePollEvent[] = []
for (const dir of pendingMarkers.keys()) {
for (const [dir, firstSeenTick] of markerProbeStartedAt) {
if (firstSeenTick === null) {
continue
}
if (tickCount - firstSeenTick > pendingMarkerMaxTicks) {
markerProbeStartedAt.set(dir, null)
continue
}
options.onPendingMarkerProbe?.(join(dir, '.git'))
if (await hasGitMarker(dir)) {
pendingMarkers.delete(dir)
markerProbeStartedAt.delete(dir)
snapshot.markers.set(dir, true)
events.push({ type: 'create', path: join(dir, '.git') })
}
@@ -267,7 +280,7 @@ async function startBasePoller(
await fullScan()
return
}
if (pendingMarkers.size > 0) {
if (markerProbeStartedAt.size > 0) {
await checkPendingMarkers()
}
}
@@ -356,5 +369,5 @@ export async function startWorktreeBaseDirectoryPoller(
options.onWatchError
)
}
return startBasePoller(target, getRepos, onEvents, pollIntervalMs, visibility, options.onFullScan)
return startBasePoller(target, getRepos, onEvents, pollIntervalMs, visibility, options)
}