mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 16:02:35 +00:00
perf: sanitize rate limit polling intervals (#4197)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user