Files
orca/src/main/github/pr-refresh-retry-state.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

54 lines
1.8 KiB
TypeScript

import type {
GitHubPRRefreshCandidate,
PRRefreshOutcome
} from '../../shared/github/pull-request-refresh-types'
import { lookupBackoffDelayMs } from '../source-control/hosted-review-refresh-pacing'
import { refreshIntervalForCandidate } from './pr-refresh-candidate-policy'
export class PRRefreshRetryState {
private readonly errorBackoff = new Map<string, { failures: number; retryAt: number }>()
private readonly manualRetryGates = new Map<string, number>()
get errorBackoffCount(): number {
return this.errorBackoff.size
}
reset(key: string): void {
this.errorBackoff.delete(key)
this.manualRetryGates.delete(key)
}
noteManualGate(key: string, outcome: PRRefreshOutcome): void {
if (outcome.kind === 'upstream-error' && outcome.retryDisabledUntil !== undefined) {
this.manualRetryGates.set(key, outcome.retryDisabledUntil)
} else {
this.manualRetryGates.delete(key)
}
}
manualGateUntil(key: string): number {
return this.manualRetryGates.get(key) ?? 0
}
nextVisibleErrorRetryAt(key: string, candidate: GitHubPRRefreshCandidate): number {
const failures = (this.errorBackoff.get(key)?.failures ?? 0) + 1
const interval = refreshIntervalForCandidate(candidate)
const retryAt =
Date.now() +
Math.max(lookupBackoffDelayMs(failures), Number.isFinite(interval) ? interval : 0)
this.errorBackoff.set(key, { failures, retryAt })
return retryAt
}
withErrorSchedule(outcome: PRRefreshOutcome, retryAt: number): PRRefreshOutcome {
if (outcome.kind !== 'upstream-error') {
return outcome
}
const cooldownUntil = outcome.retryDisabledUntil
return {
...outcome,
nextAutoRetryAt: cooldownUntil !== undefined ? Math.max(retryAt, cooldownUntil) : retryAt
}
}
}