Skip to content

Commit 3b6b656

Browse files
authored
#113 fix queue delay with exec evenly flag (#359)
* #113 fix queue delay when points consumed evenly over duration
1 parent 0df6f6c commit 3b6b656

4 files changed

Lines changed: 67 additions & 0 deletions

File tree

lib/RateLimiterMemory.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class RateLimiterMemory extends RateLimiterAbstract {
3535
if (delay < this.execEvenlyMinDelayMs) {
3636
delay = res.consumedPoints * this.execEvenlyMinDelayMs;
3737
}
38+
// Adjust msBeforeNext to reflect time already waited before resolving
39+
res.msBeforeNext = Math.max(res.msBeforeNext - delay, 0);
3840

3941
setTimeout(resolve, delay, res);
4042
} else {

lib/RateLimiterStoreAbstract.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterInsuredAbstra
8585
delay = res.consumedPoints * this.execEvenlyMinDelayMs;
8686
}
8787

88+
// Adjust msBeforeNext to reflect time already waited before resolving
89+
res.msBeforeNext = Math.max(res.msBeforeNext - delay, 0);
90+
8891
setTimeout(resolve, delay, res);
8992
} else {
9093
resolve(res);

test/RateLimiterMemory.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,40 @@ describe('RateLimiterMemory with fixed window', function RateLimiterMemoryTest()
8686
});
8787
});
8888

89+
it('updates msBeforeNext after execEvenly delay', (done) => {
90+
const testKey = 'consumeEvenlyMsBeforeNext';
91+
const rateLimiterMemory = new RateLimiterMemory({
92+
points: 2, duration: 5, execEvenly: true, execEvenlyMinDelayMs: 500,
93+
});
94+
95+
rateLimiterMemory.consume(testKey)
96+
.then((resFirst) => {
97+
const initialMsBeforeNext = resFirst.msBeforeNext;
98+
const expectedDelay = Math.ceil(initialMsBeforeNext / 2);
99+
const timeFirstConsume = Date.now();
100+
rateLimiterMemory.consume(testKey)
101+
.then((resSecond) => {
102+
const diff = Date.now() - timeFirstConsume;
103+
104+
// Real delay should be close to calculated one from formula
105+
expect(diff).to.be.greaterThan(expectedDelay - 100);
106+
expect(diff).to.be.lessThan(expectedDelay + 100);
107+
108+
// msBeforeNext should be reduced approximately by that delay
109+
expect(resSecond.msBeforeNext >= 0).to.equal(true);
110+
expect(resSecond.msBeforeNext)
111+
.to.be.closeTo(initialMsBeforeNext - expectedDelay, 100);
112+
done();
113+
})
114+
.catch((err) => {
115+
done(err);
116+
});
117+
})
118+
.catch((err) => {
119+
done(err);
120+
});
121+
});
122+
89123
it('makes penalty', (done) => {
90124
const testKey = 'penalty1';
91125
const rateLimiterMemory = new RateLimiterMemory({ points: 3, duration: 5 });

test/RateLimiterQueue.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,32 @@ describe('RateLimiterQueue with FIFO queue', function RateLimiterQueueTest() {
232232
});
233233
});
234234
});
235+
236+
it('works correctly with underlying execEvenly limiter (no extra wait from stale msBeforeNext)', (done) => {
237+
const rlMemory = new RateLimiterMemory({
238+
points: 2,
239+
duration: 1,
240+
execEvenly: true,
241+
execEvenlyMinDelayMs: 100,
242+
});
243+
const rlQueue = new RateLimiterQueue(rlMemory);
244+
const startTime = Date.now();
245+
246+
rlQueue.removeTokens(1)
247+
.then(() => {
248+
249+
rlQueue.removeTokens(1)
250+
.then(() => {
251+
const diff = Date.now() - startTime;
252+
expect(diff).to.be.closeTo(500, 100);
253+
done();
254+
})
255+
.catch((err) => {
256+
done(err);
257+
});
258+
})
259+
.catch((err) => {
260+
done(err);
261+
});
262+
});
235263
});

0 commit comments

Comments
 (0)