From c232057e54d8387e567aae22d1806c845c0d3f43 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Mon, 27 Oct 2025 22:01:54 +0100 Subject: [PATCH] Use monotonic clock for rate limiting (#8456) --- src/core/task/Task.ts | 10 +++++----- src/core/task/__tests__/Task.spec.ts | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 6028e1a9b8..508cf051d6 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2059,7 +2059,7 @@ export class Task extends EventEmitter implements TaskLike { const drainStreamInBackgroundToFindAllUsage = async (apiReqIndex: number) => { const timeoutMs = DEFAULT_USAGE_COLLECTION_TIMEOUT_MS - const startTime = Date.now() + const startTime = performance.now() const modelId = getModelId(this.apiConfiguration) // Local variables to accumulate usage data without affecting the main flow @@ -2130,7 +2130,7 @@ export class Task extends EventEmitter implements TaskLike { // Use the same iterator that the main loop was using while (!item.done) { // Check for timeout - if (Date.now() - startTime > timeoutMs) { + if (performance.now() - startTime > timeoutMs) { console.warn( `[Background Usage Collection] Timed out after ${timeoutMs}ms for model: ${modelId}, processed ${chunkCount} chunks`, ) @@ -2601,10 +2601,10 @@ export class Task extends EventEmitter implements TaskLike { // Use the shared timestamp so that subtasks respect the same rate-limit // window as their parent tasks. if (Task.lastGlobalApiRequestTime) { - const now = Date.now() + const now = performance.now() const timeSinceLastRequest = now - Task.lastGlobalApiRequestTime const rateLimit = apiConfiguration?.rateLimitSeconds || 0 - rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000) + rateLimitDelay = Math.ceil(Math.min(rateLimit, Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000)) } // Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there. @@ -2619,7 +2619,7 @@ export class Task extends EventEmitter implements TaskLike { // Update last request time before making the request so that subsequent // requests — even from new subtasks — will honour the provider's rate-limit. - Task.lastGlobalApiRequestTime = Date.now() + Task.lastGlobalApiRequestTime = performance.now() const systemPrompt = await this.getSystemPrompt() this.lastUsedInstructions = systemPrompt diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 39f426949d..b4f2e04163 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -1106,9 +1106,9 @@ describe("Cline", () => { await parentIterator.next() // Simulate time passing (more than rate limit) - const originalDateNow = Date.now - const mockTime = Date.now() + (mockApiConfig.rateLimitSeconds + 1) * 1000 - Date.now = vi.fn(() => mockTime) + const originalPerformanceNow = performance.now + const mockTime = performance.now() + (mockApiConfig.rateLimitSeconds + 1) * 1000 + performance.now = vi.fn(() => mockTime) // Create a subtask after time has passed const child = new Task({ @@ -1129,8 +1129,8 @@ describe("Cline", () => { // Verify no rate limiting was applied expect(mockDelay).not.toHaveBeenCalled() - // Restore Date.now - Date.now = originalDateNow + // Restore performance.now + performance.now = originalPerformanceNow }) it("should share rate limiting across multiple subtasks", async () => {