Skip to content

Commit eb4a19a

Browse files
committed
Add counter and cached config storage
1 parent f44a1ec commit eb4a19a

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const counters = new Map();
2+
3+
const configCache = new Map();
4+
5+
function setCounter(key, value) {
6+
// Make sure that the Map remains in order
7+
// Counters expiring soonest will be first during iteration.
8+
counters.delete(key);
9+
counters.set(key, value);
10+
}
11+
12+
function getCounter(key) {
13+
return counters.get(key);
14+
}
15+
16+
function expireCounters(now) {
17+
const toRemove = [];
18+
for (const [key, value] of counters.entries()) {
19+
if (value <= now) {
20+
toRemove.push(key);
21+
}
22+
}
23+
24+
for (const key of toRemove) {
25+
counters.delete(key);
26+
}
27+
}
28+
29+
function setCachedConfig(key, limitConfig, ttl) {
30+
const expiry = Date.now() + ttl;
31+
configCache.set(key, { expiry, config: limitConfig });
32+
}
33+
34+
function getCachedConfig(key) {
35+
const value = configCache.get(key);
36+
if (value === undefined) {
37+
return undefined;
38+
}
39+
40+
const { expiry, config } = value;
41+
if (expiry <= Date.now()) {
42+
configCache.delete(key);
43+
return undefined;
44+
}
45+
46+
return config;
47+
}
48+
49+
function expireCachedConfigs(now) {
50+
const toRemove = [];
51+
for (const [key, { expiry }] of configCache.entries()) {
52+
if (expiry <= now) {
53+
toRemove.push(key);
54+
}
55+
}
56+
57+
for (const key of toRemove) {
58+
configCache.delete(key);
59+
}
60+
}
61+
62+
module.exports = {
63+
setCounter,
64+
getCounter,
65+
expireCounters,
66+
setCachedConfig,
67+
getCachedConfig,
68+
expireCachedConfigs,
69+
70+
// Do not access directly
71+
// Used only for tests
72+
counters,
73+
configCache,
74+
};
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const assert = require('assert');
2+
const sinon = require('sinon');
3+
4+
const constants = require('../../../../../constants');
5+
const {
6+
counters,
7+
configCache,
8+
getCounter,
9+
setCounter,
10+
expireCounters,
11+
getCachedConfig,
12+
setCachedConfig,
13+
expireCachedConfigs,
14+
} = require('../../../../../lib/api/apiUtils/rateLimit/cache');
15+
16+
describe('test counter storage', () => {
17+
it('setCounter() should set a counter', () => {
18+
setCounter('foo', 10);
19+
assert.strictEqual(counters.get('foo'), 10);
20+
});
21+
22+
it('getCounter() should get a counter', () => {
23+
setCounter('foo', 10);
24+
assert.strictEqual(getCounter('foo'), 10);
25+
});
26+
27+
it('should maintain order when updating a counter', () => {
28+
setCounter('foo', 10);
29+
setCounter('bar', 20);
30+
setCounter('foo', 30);
31+
32+
const items = Array.from(counters.entries());
33+
assert.deepStrictEqual(items, [
34+
['bar', 20],
35+
['foo', 30],
36+
]);
37+
});
38+
39+
it('should expire counters less than or equal to the given timestamp', () => {
40+
const now = Date.now();
41+
const past = now - 100;
42+
const future = now + 100;
43+
setCounter('past', past);
44+
setCounter('present', now);
45+
setCounter('future', future);
46+
expireCounters(now);
47+
assert.strictEqual(getCounter('past'), undefined);
48+
assert.strictEqual(getCounter('present'), undefined);
49+
assert.strictEqual(getCounter('future'), future);
50+
});
51+
});
52+
53+
describe('test limit config cache storage', () => {
54+
const now = Date.now();
55+
56+
let clock;
57+
before(() => {
58+
clock = sinon.useFakeTimers(now);
59+
});
60+
61+
after(() => {
62+
clock.restore();
63+
});
64+
65+
it('should add config to cache', () => {
66+
setCachedConfig('foo', 10, constants.rateLimitDefaultConfigCacheTTL);
67+
assert.deepStrictEqual(
68+
configCache.get('foo'),
69+
{
70+
expiry: now + constants.rateLimitDefaultConfigCacheTTL,
71+
config: 10,
72+
}
73+
);
74+
});
75+
76+
it('should get a non expired config', () => {
77+
setCachedConfig('foo', 10, constants.rateLimitDefaultConfigCacheTTL);
78+
assert.strictEqual(getCachedConfig('foo'), 10);
79+
});
80+
81+
it('should return undefined and delete the key for an expired config', () => {
82+
configCache.set('foo', {
83+
expiry: now - 10000,
84+
config: 10,
85+
});
86+
assert.strictEqual(getCachedConfig('foo'), undefined);
87+
});
88+
89+
it('should expire configs less than or equal to the given timestamp', () => {
90+
configCache.set('past', {
91+
expiry: now - 10000,
92+
config: 10,
93+
});
94+
configCache.set('present', {
95+
expiry: now,
96+
config: 10,
97+
});
98+
configCache.set('future', {
99+
expiry: now + 10000,
100+
config: 10,
101+
});
102+
expireCachedConfigs(now);
103+
assert.strictEqual(configCache.get('past'), undefined);
104+
assert.strictEqual(configCache.get('present'), undefined);
105+
assert.deepStrictEqual(configCache.get('future'), {
106+
expiry: now + 10000,
107+
config: 10,
108+
});
109+
});
110+
});

0 commit comments

Comments
 (0)