Skip to content

Commit f4ada15

Browse files
committed
Fix a bug where timed out tokens don't pass along the timed out tokens
1 parent 1e0b477 commit f4ada15

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

spec/aimd-bucket.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,36 @@ describe("AIMDBucket", () => {
513513
bucket = new AIMDBucket({ initialRate: 10 });
514514
});
515515

516+
it("should process pending requests when tokens timeout automatically", async () => {
517+
// This test reproduces the lockup bug
518+
bucket = new AIMDBucket({
519+
initialRate: 1, // Very low rate
520+
tokenReturnTimeoutMs: 1000, // Short timeout for testing
521+
});
522+
523+
// Acquire many tokens at once - first one should be immediate, rest should be pending
524+
const tokenPromises = Array.from({ length: 5 }, () => bucket.acquire());
525+
526+
// Wait for first token to be resolved
527+
await vi.advanceTimersByTimeAsync(50);
528+
529+
const stats1 = bucket.getStatistics();
530+
expect(stats1.tokensIssued).toBe(1); // Only 1 token issued immediately
531+
expect(stats1.pendingCount).toBe(4); // 4 requests should be pending
532+
533+
// Don't complete the first token - let it timeout automatically. Advance time past the token timeout
534+
await vi.advanceTimersByTimeAsync(1100);
535+
536+
// After timeout, pending requests should start being processed due to refill
537+
await vi.advanceTimersByTimeAsync(3000); // Allow time for refill (3 more tokens at 1/sec)
538+
539+
const stats2 = bucket.getStatistics();
540+
expect(stats2.tokensIssued).toBeGreaterThan(1);
541+
expect(stats2.pendingCount).toBeLessThan(4);
542+
543+
await bucket.shutdown();
544+
});
545+
516546
it("should handle rapid acquisition bursts", async () => {
517547
// Test that we can acquire tokens up to the bucket capacity
518548
const initialTokens = await Promise.all(Array.from({ length: 10 }, () => bucket.acquire()));

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface AIMDBucketStatistics {
5454
tokensIssued: number;
5555
successCount: number;
5656
failureCount: number;
57+
pendingCount: number;
5758
rateLimitedCount: number;
5859
timeoutCount: number;
5960
successRate: number;
@@ -247,6 +248,7 @@ export class AIMDBucket {
247248
failureCount,
248249
rateLimitedCount,
249250
timeoutCount,
251+
pendingCount: this.pending.length,
250252
successRate: total > 0 ? successCount / total : 0,
251253
};
252254
}
@@ -282,6 +284,8 @@ export class AIMDBucket {
282284
_onTokenTimeout(): void {
283285
this.recentOutcomes.push({ timestamp: Date.now(), outcome: "timeout" });
284286
this._adjustRate();
287+
// Process pending requests in case rate adjustment or time passage made tokens available
288+
this._processPending();
285289
}
286290

287291
private _validate(): void {

0 commit comments

Comments
 (0)