Files
orca/src/main/github/pr-refresh-pacing.ts
T
Neilandggbdpq a2f197fde7 Refresh visible reviews automatically and stop settled merged polling (#25788)
Refresh only actual on-screen review cards and the selected visible review panel, with a single metadata owner per execution host. Open reviews refresh every 60 seconds when selected and 120 seconds otherwise. Settled merged reviews stop automatic refresh; pending merged checks continue, hidden rows stop, and work changes or stale re-exposure discover fresh state.

Remove the polling setting. Preserve provider/SSH/runtime ownership, mixed-version cache behavior, failure backoff, bounded foreground admission, request coalescing, and mutation invalidation. Cover older persisted caches and unknown HEADs.

Validated with 1,628 focused tests, typechecking, lint/code-quality gates, hidden Electron viewport checks, and parallel Codex/Claude Opus 5.5 adversarial reviews. Measurements and policy details are in PR #25788.

Fixes #25746

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
2026-10-06 00:53:49 -07:00

123 lines
4.0 KiB
TypeScript

import type { PRRefreshQueueEntry } from './pr-refresh-queue'
const BACKGROUND_BUDGET_WINDOW_MS = 5 * 60_000
const MIN_BACKGROUND_SPACING_MS = 10_000
const BACKGROUND_BUDGET_MAX = 20
const ACTIVE_BURST_WINDOW_MS = 30_000
const ACTIVE_BURST_MAX = 3
export function usesActiveRefreshPacing(entry: PRRefreshQueueEntry): boolean {
// Selected admission survives deselection while queued; periodic follow-ups clear the flag.
return (
entry.reason === 'active' ||
(entry.reason === 'visible' && entry.priority >= 80 && entry.bypassBackgroundBudget === true)
)
}
export class PRRefreshPacing {
private readonly backgroundStarts: number[] = []
private readonly activeStartsByScope = new Map<string, number[]>()
private lastBackgroundStartAt = 0
clearActiveBurstWindow(windowId: number): void {
const windowPrefix = `${windowId}::`
for (const scope of Array.from(this.activeStartsByScope.keys())) {
if (scope.startsWith(windowPrefix)) {
this.activeStartsByScope.delete(scope)
}
}
}
noteBackgroundStart(): void {
const now = Date.now()
this.lastBackgroundStartAt = now
this.backgroundStarts.push(now)
this.pruneBackgroundStarts(now)
}
nextBudgetDelay(): number {
const now = Date.now()
this.pruneBackgroundStarts(now)
const spacingDelay =
this.lastBackgroundStartAt > 0
? Math.max(0, MIN_BACKGROUND_SPACING_MS - (now - this.lastBackgroundStartAt))
: 0
const windowDelay =
this.backgroundStarts.length < BACKGROUND_BUDGET_MAX
? 0
: Math.max(1_000, BACKGROUND_BUDGET_WINDOW_MS - (now - this.backgroundStarts[0]))
return Math.max(spacingDelay, windowDelay)
}
activeOrder(a: PRRefreshQueueEntry, b: PRRefreshQueueEntry): number {
if (!usesActiveRefreshPacing(a) || !usesActiveRefreshPacing(b)) {
return 0
}
if (this.activeBurstScope(a) !== this.activeBurstScope(b)) {
return 0
}
return b.queuedAt - a.queuedAt
}
entryDelay(entry: PRRefreshQueueEntry): number {
const activeDelay = usesActiveRefreshPacing(entry) ? this.nextActiveBurstDelay(entry) : 0
if (activeDelay > 0) {
return activeDelay
}
return entry.bypassBackgroundBudget !== true &&
(entry.reason === 'visible' || entry.reason === 'swr')
? this.nextBudgetDelay()
: 0
}
isActiveBurstDelayed(entry: PRRefreshQueueEntry): boolean {
return usesActiveRefreshPacing(entry) && this.nextActiveBurstDelay(entry) > 0
}
noteActiveStart(entry: PRRefreshQueueEntry): void {
const now = Date.now()
const scope = this.activeBurstScope(entry)
const activeStarts = this.pruneActiveStarts(scope, now)
activeStarts.push(now)
this.activeStartsByScope.set(scope, activeStarts)
}
private pruneBackgroundStarts(now: number): void {
while (
this.backgroundStarts.length > 0 &&
now - this.backgroundStarts[0] > BACKGROUND_BUDGET_WINDOW_MS
) {
this.backgroundStarts.shift()
}
}
private activeBurstScope(entry: PRRefreshQueueEntry): string {
const runtimeScope = entry.candidate.connectionId
? `ssh:${entry.candidate.connectionId}`
: `local:${entry.candidate.localGitOptions?.wslDistro ?? 'host'}`
return `${entry.windowId ?? 'global'}::${runtimeScope}`
}
private pruneActiveStarts(scope: string, now: number): number[] {
const activeStarts = this.activeStartsByScope.get(scope) ?? []
while (activeStarts.length > 0 && now - activeStarts[0] >= ACTIVE_BURST_WINDOW_MS) {
activeStarts.shift()
}
if (activeStarts.length === 0) {
this.activeStartsByScope.delete(scope)
} else {
this.activeStartsByScope.set(scope, activeStarts)
}
return activeStarts
}
private nextActiveBurstDelay(entry: PRRefreshQueueEntry): number {
const now = Date.now()
const activeStarts = this.pruneActiveStarts(this.activeBurstScope(entry), now)
if (activeStarts.length < ACTIVE_BURST_MAX) {
return 0
}
return Math.max(1, ACTIVE_BURST_WINDOW_MS - (now - activeStarts[0]))
}
}