rate-limiter-flexible 11.1.0 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -68,11 +68,19 @@ module.exports = class RateLimiterAbstract {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
get execEvenlyMinDelayMs() {
|
|
71
|
-
return this._execEvenlyMinDelayMs
|
|
71
|
+
return this._execEvenlyMinDelayMs === undefined
|
|
72
|
+
? this._getExecEvenlyMinDelayMsDefault()
|
|
73
|
+
: this._execEvenlyMinDelayMs;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
set execEvenlyMinDelayMs(value) {
|
|
75
|
-
this._execEvenlyMinDelayMs =
|
|
77
|
+
this._execEvenlyMinDelayMs = value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_getExecEvenlyMinDelayMsDefault() {
|
|
81
|
+
return this.points > 0
|
|
82
|
+
? Math.ceil(this.msDuration / this.points)
|
|
83
|
+
: 0;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
get keyPrefix() {
|
package/lib/RateLimiterMemory.js
CHANGED
|
@@ -29,7 +29,7 @@ class RateLimiterMemory extends RateLimiterAbstract {
|
|
|
29
29
|
res = this._memoryStorage.set(rlKey, res.consumedPoints, this.blockDuration);
|
|
30
30
|
}
|
|
31
31
|
reject(res);
|
|
32
|
-
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
|
|
32
|
+
} else if (this.execEvenly && this.points > 0 && res.msBeforeNext > 0 && !res.isFirstInDuration) {
|
|
33
33
|
// Execute evenly
|
|
34
34
|
let delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
|
|
35
35
|
if (delay < this.execEvenlyMinDelayMs) {
|
|
@@ -262,7 +262,13 @@ class RateLimiterPostgres extends RateLimiterStoreAbstract {
|
|
|
262
262
|
|
|
263
263
|
_query(q) {
|
|
264
264
|
const prefix = this.tableName.toLowerCase();
|
|
265
|
-
const queryObj = {
|
|
265
|
+
const queryObj = { text: q.text, values: q.values };
|
|
266
|
+
// Only name the prepared statement when a name is provided. The one-off
|
|
267
|
+
// create-table query passes no name, and naming it `${prefix}:undefined`
|
|
268
|
+
// pollutes the prepared-statement cache (see #196).
|
|
269
|
+
if (q.name) {
|
|
270
|
+
queryObj.name = `${prefix}:${q.name}`;
|
|
271
|
+
}
|
|
266
272
|
return new Promise((resolve, reject) => {
|
|
267
273
|
this._getConnection()
|
|
268
274
|
.then((conn) => {
|
package/lib/RateLimiterQueue.js
CHANGED
|
@@ -25,7 +25,7 @@ module.exports = class RateLimiterQueue {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
removeTokens(tokens, key = KEY_DEFAULT) {
|
|
28
|
+
removeTokens(tokens, key = KEY_DEFAULT, expiresUnixAt = 0) {
|
|
29
29
|
if (!this._queueLimiters[key]) {
|
|
30
30
|
this._queueLimiters[key] = new RateLimiterQueueInternal(
|
|
31
31
|
this._limiterFlexible, {
|
|
@@ -34,7 +34,7 @@ module.exports = class RateLimiterQueue {
|
|
|
34
34
|
})
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
return this._queueLimiters[key].removeTokens(tokens)
|
|
37
|
+
return this._queueLimiters[key].removeTokens(tokens, expiresUnixAt)
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -48,6 +48,12 @@ class RateLimiterQueueInternal {
|
|
|
48
48
|
this._waitTimeout = null;
|
|
49
49
|
this._queue = [];
|
|
50
50
|
this._limiterFlexible = limiterFlexible;
|
|
51
|
+
// Set to true once a request carrying an expiration deadline
|
|
52
|
+
// (expiresUnixAt > 0) has been queued. While false, _processFIFO skips the
|
|
53
|
+
// expiry sweep entirely, so projects that never pass expiresUnixAt pay no
|
|
54
|
+
// extra cost. It is only ever set, never cleared by the sweep (see
|
|
55
|
+
// _processFIFO for why clearing it would be unsafe).
|
|
56
|
+
this._hasExpiringRequests = false;
|
|
51
57
|
|
|
52
58
|
this._maxQueueSize = opts.maxQueueSize
|
|
53
59
|
}
|
|
@@ -59,7 +65,7 @@ class RateLimiterQueueInternal {
|
|
|
59
65
|
})
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
removeTokens(tokens) {
|
|
68
|
+
removeTokens(tokens, expiresUnixAt = 0) {
|
|
63
69
|
const _this = this;
|
|
64
70
|
|
|
65
71
|
return new Promise((resolve, reject) => {
|
|
@@ -69,7 +75,7 @@ class RateLimiterQueueInternal {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
if (_this._queue.length > 0) {
|
|
72
|
-
_this._queueRequest.call(_this, resolve, reject, tokens);
|
|
78
|
+
_this._queueRequest.call(_this, resolve, reject, tokens, expiresUnixAt);
|
|
73
79
|
} else {
|
|
74
80
|
_this._limiterFlexible.consume(_this._key, tokens)
|
|
75
81
|
.then((res) => {
|
|
@@ -79,7 +85,7 @@ class RateLimiterQueueInternal {
|
|
|
79
85
|
if (rej instanceof Error) {
|
|
80
86
|
reject(rej);
|
|
81
87
|
} else {
|
|
82
|
-
_this._queueRequest.call(_this, resolve, reject, tokens);
|
|
88
|
+
_this._queueRequest.call(_this, resolve, reject, tokens, expiresUnixAt);
|
|
83
89
|
if (_this._waitTimeout === null) {
|
|
84
90
|
_this._waitTimeout = setTimeout(_this._processFIFO.bind(_this), rej.msBeforeNext);
|
|
85
91
|
}
|
|
@@ -89,10 +95,13 @@ class RateLimiterQueueInternal {
|
|
|
89
95
|
})
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
_queueRequest(resolve, reject, tokens) {
|
|
98
|
+
_queueRequest(resolve, reject, tokens, expiresUnixAt = 0) {
|
|
93
99
|
const _this = this;
|
|
94
100
|
if (_this._queue.length < _this._maxQueueSize) {
|
|
95
|
-
_this._queue.push({resolve, reject, tokens});
|
|
101
|
+
_this._queue.push({resolve, reject, tokens, expiresUnixAt});
|
|
102
|
+
if (expiresUnixAt > 0) {
|
|
103
|
+
_this._hasExpiringRequests = true;
|
|
104
|
+
}
|
|
96
105
|
} else {
|
|
97
106
|
reject(new RateLimiterQueueError(`Number of requests reached it's maximum ${_this._maxQueueSize}`))
|
|
98
107
|
}
|
|
@@ -106,6 +115,28 @@ class RateLimiterQueueInternal {
|
|
|
106
115
|
_this._waitTimeout = null;
|
|
107
116
|
}
|
|
108
117
|
|
|
118
|
+
// Reject any queued requests that have reached their expiration deadline
|
|
119
|
+
// (expiresUnixAt, in Unix seconds) before they could be fulfilled. The
|
|
120
|
+
// sweep is skipped until a request with a deadline has been queued, so
|
|
121
|
+
// projects that never pass expiresUnixAt keep the original O(1) cost here.
|
|
122
|
+
//
|
|
123
|
+
// The flag is deliberately only ever set, never cleared from a snapshot of
|
|
124
|
+
// _queue: while a request is being consumed it is momentarily shift()ed out
|
|
125
|
+
// of _queue and may be unshift()ed back by the rate-limit retry path below
|
|
126
|
+
// (without going through _queueRequest). Clearing the flag from a snapshot
|
|
127
|
+
// that excludes such an in-flight request could strand it with the sweep
|
|
128
|
+
// permanently disabled, so it would never expire.
|
|
129
|
+
if (_this._hasExpiringRequests) {
|
|
130
|
+
const nowSecs = Math.floor(Date.now() / 1000);
|
|
131
|
+
_this._queue = _this._queue.filter((item) => {
|
|
132
|
+
if (item.expiresUnixAt > 0 && nowSecs >= item.expiresUnixAt) {
|
|
133
|
+
item.reject(new RateLimiterQueueError('The request to remove tokens expired before it could be fulfilled'));
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
109
140
|
if (_this._queue.length === 0) {
|
|
110
141
|
return;
|
|
111
142
|
}
|
|
@@ -79,7 +79,7 @@ module.exports = class RateLimiterStoreAbstract extends RateLimiterInsuredAbstra
|
|
|
79
79
|
.catch((err) => {
|
|
80
80
|
reject(err);
|
|
81
81
|
});
|
|
82
|
-
} else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) {
|
|
82
|
+
} else if (this.execEvenly && this.points > 0 && res.msBeforeNext > 0 && !res.isFirstInDuration) {
|
|
83
83
|
let delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
|
|
84
84
|
if (delay < this.execEvenlyMinDelayMs) {
|
|
85
85
|
delay = res.consumedPoints * this.execEvenlyMinDelayMs;
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -514,7 +514,16 @@ export class RateLimiterQueue {
|
|
|
514
514
|
|
|
515
515
|
getTokensRemaining(key?: string | number): Promise<number>;
|
|
516
516
|
|
|
517
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Remove tokens from the queue.
|
|
519
|
+
*
|
|
520
|
+
* @param tokens Number of tokens to remove.
|
|
521
|
+
* @param key Optional queue key for separate FIFO queues.
|
|
522
|
+
* @param expiresUnixAt Optional absolute deadline as a Unix timestamp in
|
|
523
|
+
* seconds. If the request is still queued when this time is reached, it is
|
|
524
|
+
* rejected with a `RateLimiterQueueError`. Defaults to `0` (never expires).
|
|
525
|
+
*/
|
|
526
|
+
removeTokens(tokens: number, key?: string | number, expiresUnixAt?: number): Promise<number>;
|
|
518
527
|
}
|
|
519
528
|
|
|
520
529
|
export class BurstyRateLimiter {
|