perf: sanitize rate limit polling intervals (#4197)

This commit is contained in:
Neil
2026-05-31 07:53:28 -07:00
committed by GitHub
parent b7290aba8e
commit 834559bf9e
2 changed files with 35 additions and 1 deletions
+25
View File
@@ -170,6 +170,31 @@ describe('RateLimitService', () => {
expect(secondWindow.listenerCount('closed')).toBe(0)
})
it('sanitizes renderer-provided polling intervals before scheduling timers', () => {
vi.useFakeTimers()
const intervalSpy = vi.spyOn(globalThis, 'setInterval')
try {
vi.mocked(fetchClaudeRateLimits).mockResolvedValue(okProvider('claude', 12))
vi.mocked(fetchCodexRateLimits).mockResolvedValue(okProvider('codex', 24))
const service = new RateLimitService()
service.setPollingInterval(Number.NaN)
service.start()
expect(intervalSpy).toHaveBeenLastCalledWith(expect.any(Function), 15 * 60 * 1000)
service.setPollingInterval(Number.MAX_SAFE_INTEGER)
expect(intervalSpy).toHaveBeenLastCalledWith(expect.any(Function), 2_147_483_647)
service.setPollingInterval(10)
expect(intervalSpy).toHaveBeenLastCalledWith(expect.any(Function), 30_000)
service.stop()
} finally {
intervalSpy.mockRestore()
vi.useRealTimers()
}
})
it('keeps recent stale data across repeated failures', async () => {
const service = new RateLimitService()
const internal = serviceInternals(service)
+10 -1
View File
@@ -38,6 +38,8 @@ type ClaudeAuthPreparationResolver = (
// state is informational, so prefer keeping a recent snapshot over polling it
// into 429s during long focused Orca sessions.
const DEFAULT_POLL_MS = 15 * 60 * 1000 // 15 minutes
const MIN_POLL_MS = 30 * 1000 // 30 seconds — renderer input should never create a tight loop.
const MAX_POLL_MS = 2_147_483_647 // Max safe setInterval delay before Node clamps back to 1ms.
const MIN_REFETCH_MS = 5 * 60 * 1000 // 5 minutes — debounce resume/manual refresh bursts
const STALE_THRESHOLD_MS = 30 * 60 * 1000 // 30 minutes — after this, stale data is dropped
const INACTIVE_FETCH_DEBOUNCE_MS = 60 * 1000 // 60 seconds — debounce fetch-on-open
@@ -51,6 +53,13 @@ type InternalRateLimitState = {
opencodeGo: ProviderRateLimits | null
}
function normalizePollingInterval(ms: number): number {
if (!Number.isFinite(ms)) {
return DEFAULT_POLL_MS
}
return Math.min(MAX_POLL_MS, Math.max(MIN_POLL_MS, ms))
}
export class RateLimitService {
private state: InternalRateLimitState = {
claude: null,
@@ -498,7 +507,7 @@ export class RateLimitService {
}
setPollingInterval(ms: number): void {
this.pollInterval = Math.max(30_000, ms)
this.pollInterval = normalizePollingInterval(ms)
if (this.timer) {
this.stopTimer()
this.startTimer()