From 8859e73980d2cf81eaad465b258bbb13d54aab8e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:24:33 -0700 Subject: [PATCH] perf(main): retire stale worktree marker probes (#13437) --- ...worktree-base-pending-marker-benchmark.mjs | 117 ++++++++++++++++++ .../worktree-base-directory-poller.test.ts | 61 +++++++++ .../ipc/worktree-base-directory-poller.ts | 43 ++++--- 3 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 config/scripts/worktree-base-pending-marker-benchmark.mjs diff --git a/config/scripts/worktree-base-pending-marker-benchmark.mjs b/config/scripts/worktree-base-pending-marker-benchmark.mjs new file mode 100644 index 00000000000..9240acd22ad --- /dev/null +++ b/config/scripts/worktree-base-pending-marker-benchmark.mjs @@ -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 }) +} diff --git a/src/main/ipc/worktree-base-directory-poller.test.ts b/src/main/ipc/worktree-base-directory-poller.test.ts index 09cda0ed480..e28823adbc8 100644 --- a/src/main/ipc/worktree-base-directory-poller.test.ts +++ b/src/main/ipc/worktree-base-directory-poller.test.ts @@ -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() diff --git a/src/main/ipc/worktree-base-directory-poller.ts b/src/main/ipc/worktree-base-directory-poller.ts index 2f65e0fc282..b5076c2ed7f 100644 --- a/src/main/ipc/worktree-base-directory-poller.ts +++ b/src/main/ipc/worktree-base-directory-poller.ts @@ -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 { 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 | null = null let parkedWhileHidden = false - // dir → tick when first seen without a `.git` marker - const pendingMarkers = new Map() + const pendingMarkerMaxTicks = options.pendingMarkerMaxTicks ?? PENDING_MARKER_MAX_TICKS + // dir → first probe tick; null means backstop scans only + const markerProbeStartedAt = new Map() for (const [dir, marker] of snapshot.markers) { if (!marker) { - pendingMarkers.set(dir, 0) + markerProbeStartedAt.set(dir, 0) } } const fullScan = async (): Promise => { - 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 => { 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) }