mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
perf(main): retire stale worktree marker probes (#13437)
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user