Use monotonic clock for rate limiting (#8456)

This commit is contained in:
Christiaan Arnoldus 2025-10-27 22:01:54 +01:00 • committed by GitHub
parent bde2c3cec6
commit c232057e54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 10 additions and 10 deletions

View file

@ -2059,7 +2059,7 @@ export class Task extends EventEmitter<TaskEvents> 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<TaskEvents> 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<TaskEvents> 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<TaskEvents> 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

View file

@ -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 () => {