plugin-throttling.js
plugin-throttling.js copied to clipboard
`retryCount` randomly has a wrong value
TLDR;
In case a request hit the rate limit, plugin does its job 👌 .
But if a second request also hit the rate limit (either for token 2 or because that request also uses token 1) while other one is waiting, my rate-limiter
listener receives a options.request.retryCount
at 1
instead of 0
So request is not retried, the HTTP error is thrown, and the whole process stop.
Hello,
I have a really strange behavior with the plugin, I wonder if it comes from the fact I use 2 different clients or the fact to use Promise.all
🤔
Each client has a dedicated token, and each function using github api use a dedicated client (looping from first to the second, then first again, then second again, etc)
When I receive an HTTP 403 error :
- plugin is correctly called
- plugin correctly call
rate-limiter
listener - my
rate-limiter
listener asks for retry
At this point, everything works. In case I hit the rate limit at the beginning of the process, there is no issue, request is retried correctly after the retryAfter
time.
But there is cases where I call a methods using octokit client and push the resulting promise inside an array and do that X times. Then I await
a Promise.all
of the array of promise.
As said before, each call will use a dedicated client (first or second) and so a dedicated token.
In case a request hit the rate limit, plugin does its job 👌 .
But if a second request also hit the rate limit (either for token 2 or because that request also uses token 1) while other one is waiting, my rate-limiter
listener receives a options.request.retryCount
at 1
instead of 0
So request is not retried, the HTTP error is thrown, and the whole process stop.
Do you know if plugin works in that configuration ?
N.B. :
- I already tried to set an
id
for each instance underthrottling
key => no change - I tried to inject a dedicated
Bottleneck
light instance (usingthrottling.retryLimiter
key) => no change - I tried to set an unique ID for each
Bottleneck
light instance too => no change
Packages versions :
-
@octokit/auth-token
=>2.4.5
-
@octokit/core
=>3.5.1
-
@octokit/endpoint
=>6.0.12
-
@octokit/graphql
=>4.8.0
-
@octokit/openapi-types
=>9.4.0
-
@octokit/plugin-paginate-rest
=>2.6.0
-
@octokit/plugin-request-log
=>1.0.2
-
@octokit/plugin-rest-endpoint-methods
=>4.2.1
-
@octokit/plugin-throttling
=>3.5.1
Root requirement -
@octokit/request
=>5.6.1
-
@octokit/request-error
=>2.1.0
-
@octokit/rest
=>18.0.9
Root requirement -
@octokit/types
=>6.24.0
Root requirement -
bottleneck
=>2.19.5
Root requirement
Current implementation :
import { throttling } from '@octokit/plugin-throttling';
import { Octokit } from '@octokit/rest'; // <-- Not @octokit/core, like on your doc
import BottleneckLight from 'bottleneck/light';
import * as uuid from 'uuid';
type RetryOptions = {
method: string;
url: string;
request: {
retryCount: number;
};
};
const tokens = {token: 'XXXXXX', token1: 'YYYYYYYY'};
const baseThrottleOptions = {
onRateLimit: (retryAfter: number, options: RetryOptions): null | true => {
const context = {
errorType,
method: options.method,
url: options.url,
retryCount: options.request.retryCount,
retryAfter,
};
console.warn(context, `Throttle plugin error : ${errorType}`);
if (options.request.retryCount < 1) {
// only retries once
console.info(context, `Retrying request !`);
return true;
}
console.error(context, `Request already retried, giving up`);
return null;
},
onAbuseLimit: .... // Same basic listener as above
};
const gitHubClients = Object.keys(tokens).map((key) => {
const token = tokens[key];
const CustomOctokit = Octokit.plugin(throttling);
const id = `${uuid()}-ID>>${key}<<`;
const instance = new CustomOctokit({
auth: token,
throttle: {
...baseThrottleOptions,
id,
retryLimiter: new BottleneckLight({ id: `${id}-BottleneckLight` }),
},
});
return instance;
});
I also patched node_modules
lib files in order to add ids and debug statements everywhere, see below what I found so far (FYI: all of this is made within the same second):
-
Bottleneck job
<no-id>-yy2p6xzj4eb
Bottleneck queueQUEUE-uzvp13sxylo
Bottleneck instanceoctokit-global-427913a9-c793-406d-9964-3999912f835d-ID>>token1<<
- I guess it the Bottleneck instance created by octokit (not by your plugin)
x-github-request-id
for failing response:A16A:58C8:D89F80:E50A53:613F4287
Url of failing request :https://api.github.com/repos/OWNER/REPO_1/contents/.github/PULL_REQUEST_TEMPLATE
==> No listener call (Not enough time to execute listener OR is master job of Bottleneck job <no-id>-lpreittunul
?)
- Bottleneck job
<no-id>-crqa4hcsngi
Bottleneck queueQUEUE-uzvp13sxylo
Bottleneck instanceoctokit-global-427913a9-c793-406d-9964-3999912f835d-ID>>token1<<
x-github-request-id
for failing response:8431:F763:FBB74:1010CA:613F53B9
Url of failing request :https://api.github.com/repos/OWNER/REPO_1/contents/pull_request_template.md
==> No listener call (Not enough time to execute listener OR is master job of Bottleneck job <no-id>-gz4915z0qbl
?)
- Bottleneck job
<no-id>-lpreittunul
Bottleneck queueQUEUE-xa5jdp0wx2c
Bottleneck instance427913a9-c793-406d-9964-3999912f835d-ID>>token1<<-BottleneckLight
x-github-request-id
for failing response:DA11:CC27:103F7E:109607:613F53B9
Url of failing request :https://api.github.com/repos/OWNER/REPO_1/contents/.github/PULL_REQUEST_TEMPLATE
=> Listener call OK, ask for retry
- Bottleneck job
<no-id>-gz4915z0qbl
Bottleneck queueQUEUE-xa5jdp0wx2c
Bottleneck instance427913a9-c793-406d-9964-3999912f835d-ID>>token1<<-BottleneckLight
x-github-request-id
for failing response:8431:F763:FBB74:1010CA:613F53B9
Url of failing request :https://api.github.com/repos/OWNER/REPO_1/contents/pull_request_template.md
=> Listener call OK, give up as retryCount=1
As you can see, both requests use the same token (>>token1<<
), so it makes sens that second one fails.
But I would expect the second request to have a retryCount
at 0, not 1
BTW, the retryCount
value of Bottleneck job is 0 at this point, but listener can't access that value
In case it helps, see below patch for the two node_module lib files:
tmp/node_modules/xxx
is used to replace the corresponding file when docker image is created
@octokit/plugin-throttling/dist-node/index.js
index 0ccf157..272a860 100644
--- a/node_modules/@octokit/plugin-throttling/dist-node/index.js
+++ b/tmp/node_modules/@octokit/plugin-throttling/dist-node/index.js
@@ -184,6 +184,7 @@ const createGroups = function (Bottleneck, common) {
};
function throttling(octokit, octokitOptions = {}) {
+ console.log('DEBUG - PLUGIN: throttling(octokit, octokitOptions = {})');
const {
enabled = true,
Bottleneck = BottleneckLight,
@@ -195,6 +196,7 @@ function throttling(octokit, octokitOptions = {}) {
} = octokitOptions.throttle || {};
if (!enabled) {
+ console.log(`DEBUG - PLUGIN (${id}): Disabled !!!`);
return {};
}
@@ -212,11 +214,13 @@ function throttling(octokit, octokitOptions = {}) {
triggersNotification,
minimumAbuseRetryAfter: 5,
retryAfterBaseValue: 1000,
- retryLimiter: new Bottleneck(),
+ retryLimiter: new Bottleneck({id: 'DEFAULT'}),
id
}, groups), // @ts-ignore
octokitOptions.throttle);
+ console.log(`DEBUG - PLUGIN (${id}): Bottleneck ID "${state.retryLimiter.id}"`);
+
if (typeof state.onAbuseLimit !== "function" || typeof state.onRateLimit !== "function") {
throw new Error(`octokit/plugin-throttling error:
You must pass the onAbuseLimit and onRateLimit error handlers.
@@ -237,10 +241,20 @@ function throttling(octokit, octokitOptions = {}) {
events.on("abuse-limit", state.onAbuseLimit); // @ts-ignore
events.on("rate-limit", state.onRateLimit); // @ts-ignore
+ events.on("debug", (msg, infos) => {
+ console.log(`DEBUG - PLUGIN (${id}): DEBUG EVENT =>`);
+ console.log({msg, infos});
+ }); // @ts-ignore
events.on("error", e => console.warn("Error in throttling-plugin limit handler", e)); // @ts-ignore
+ console.log(`DEBUG - PLUGIN (${id}): INSTALL LISTENERS`);
state.retryLimiter.on("failed", async function (error, info) {
+ console.log(
+ `DEBUG - PLUGIN (${id}): ON FAILED`,
+ {error, info, options: info.args[info.args.length - 1], request: info.args[info.args.length - 1].request, response: error.response}
+ );
+
const options = info.args[info.args.length - 1];
const {
pathname
@@ -248,6 +262,8 @@ function throttling(octokit, octokitOptions = {}) {
const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
if (!(shouldRetryGraphQL || error.status === 403)) {
+ console.log(`DEBUG - PLUGIN (${id}): ARGHH !!! EXIT 1`);
+
return;
}
@@ -257,7 +273,9 @@ function throttling(octokit, octokitOptions = {}) {
wantRetry,
retryAfter
} = await async function () {
+ console.log(`DEBUG - PLUGIN (${id}): EXECUTE CALLBACKS`);
if (/\babuse\b/i.test(error.message)) {
+ console.log(`DEBUG - PLUGIN (${id}): EXECUTE ABUSE CALLBACK`);
// The user has hit the abuse rate limit. (REST and GraphQL)
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api#abuse-rate-limits
// The Retry-After header can sometimes be blank when hitting an abuse limit,
@@ -271,6 +289,7 @@ function throttling(octokit, octokitOptions = {}) {
}
if (error.response.headers != null && error.response.headers["x-ratelimit-remaining"] === "0") {
+ console.log(`DEBUG - PLUGIN (${id}): EXECUTE RATE LIMIT CALLBACK`);
// The user has used all their allowed calls for the current time period (REST and GraphQL)
// https://docs.github.com/en/rest/reference/rate-limit (REST)
// https://docs.github.com/en/graphql/overview/resource-limitations#rate-limit (GraphQL)
@@ -286,12 +305,14 @@ function throttling(octokit, octokitOptions = {}) {
return {};
}();
+ console.log(`DEBUG - PLUGIN (${id}): wantRetry : `+ (wantRetry ? 'T' : 'F'));
if (wantRetry) {
options.request.retryCount++; // @ts-ignore
return retryAfter * state.retryAfterBaseValue;
}
});
+ console.log(`DEBUG - PLUGIN (${id}): INSTALL LISTENERS - OK`);
octokit.hook.wrap("request", wrapRequest.bind(null, state));
return {};
}
node_modules/bottleneck/light.js
index c4aa265..9150c17 100644
--- a/node_modules/bottleneck/light.js
+++ b/tmp/node_modules/bottleneck/light.js
@@ -231,7 +231,9 @@
Events$1 = Events_1;
Queues = class Queues {
- constructor(num_priorities) {
+ constructor(num_priorities, debugId='UNKNOWN') {
+ this.debugId = debugId;
+ console.log(`DEBUG - BOTTLENECK QUEUE (${this.debugId}): CONSTRUCT`);
var i;
this.Events = new Events$1(this);
this._length = 0;
@@ -262,6 +264,7 @@
}
push(job) {
+ console.log(`DEBUG - BOTTLENECK QUEUE (${this.debugId}): PUSH JOB "${job.options.id}"`);
return this._lists[job.options.priority].push(job);
}
@@ -402,14 +405,19 @@
eventInfo = {args: this.args, options: this.options, retryCount: this.retryCount};
this.Events.trigger("executing", eventInfo);
try {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): PASSED ?`);
passed = (await (chained != null ? chained.schedule(this.options, this.task, ...this.args) : this.task(...this.args)));
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): PASSED OK`);
if (clearGlobalState()) {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): clearGlobalState()`);
this.doDone(eventInfo);
await free(this.options, eventInfo);
this._assertStatus("DONE");
return this._resolve(passed);
}
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): HUMMMMM ?????`);
} catch (error1) {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): ERROR 1 !!!!`, {error1})
error = error1;
return this._onFailure(error, eventInfo, clearGlobalState, run, free);
}
@@ -427,15 +435,20 @@
}
async _onFailure(error, eventInfo, clearGlobalState, run, free) {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): _onFailure`)
var retry, retryAfter;
if (clearGlobalState()) {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): listenerCount = `+(this.Events.listenerCount("failed")));
retry = (await this.Events.trigger("failed", error, eventInfo));
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): retry = `+(retry != null ? 'T' : 'F'));
if (retry != null) {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): retry -> OK`);
retryAfter = ~~retry;
this.Events.trigger("retry", `Retrying ${this.options.id} after ${retryAfter} ms`, eventInfo);
this.retryCount++;
return run(retryAfter);
} else {
+ console.log(`DEBUG - BOTTLENECK JOB (${this.options.id}): retry -> ARGHHH !`);
this.doDone(eventInfo);
await free(this.options, eventInfo);
this._assertStatus("DONE");
@@ -1063,7 +1076,8 @@
this._addToQueue = this._addToQueue.bind(this);
this._validateOptions(options, invalid);
parser$5.load(options, this.instanceDefaults, this);
- this._queues = new Queues$1(NUM_PRIORITIES$1);
+ this.queueDebugId = 'QUEUE-'+Math.random().toString(36).slice(2);
+ this._queues = new Queues$1(NUM_PRIORITIES$1, this.queueDebugId);
this._scheduled = {};
this._states = new States$1(["RECEIVED", "QUEUED", "RUNNING", "EXECUTING"].concat(this.trackDoneStatus ? ["DONE"] : []));
this._limiter = null;
@@ -1090,6 +1104,7 @@
var ref;
return (ref = this._store.heartbeat) != null ? typeof ref.unref === "function" ? ref.unref() : void 0 : void 0;
});
+ console.log(`DEBUG - BOTTLENECK (${this.id} / QUEUE ID=${this.queueDebugId}): CONSTRUCT`);
}
_validateOptions(options, invalid) {
@@ -1382,6 +1397,7 @@
});
};
job = new Job$1(task, args, options, this.jobDefaults, this.rejectOnDrop, this.Events, this._states, this.Promise);
+ console.log(`DEBUG - BOTTLENECK (${this.id} / QUEUE ID=${this.queueDebugId}): submit -> Created Job "${job.options.id}"`);
job.promise.then(function(args) {
return typeof cb === "function" ? cb(...args) : void 0;
}).catch(function(args) {
@@ -1403,6 +1419,7 @@
[options, task, ...args] = args;
}
job = new Job$1(task, args, options, this.jobDefaults, this.rejectOnDrop, this.Events, this._states, this.Promise);
+ console.log(`DEBUG - BOTTLENECK (${this.id} / QUEUE ID=${this.queueDebugId}): schedule -> Created Job "${job.options.id}"`);
this._receive(job);
return job.promise;
}
Does it work with multiple parallel calls and/or multiple clients
Short answer, yes :)
Do the tokens belong to the same user account / app? If so, then you have to pass a single bottleneck instance to all of them, that way the throttling will work across the instances.
But if a second request also hit the rate limit (either for token 2 or because that request also uses token 1) while other one is waiting, my
rate-limiter
listener receives aoptions.request.retryCount
at1
instead of0
So request is not retried, the HTTP error is thrown, and the whole process stop.Do you know if plugin works in that configuration ?
There might be a bug with retryCount
, but it's hard to say with your code, we would need to isolate the problem further.
Thanks for the quick answer :)
Do the tokens belong to the same user account / app? If so, then you have to pass a single bottleneck instance to all of them, that way the throttling will work across the instances.
Nop, tokens belong to two different users (it was also my guess at beginning, but I double checked and it's not the case).
There might be a bug with retryCount, but it's hard to say with your code, we would need to isolate the problem further.
I guess yes, but I spent 3 days on that issue without being able to figure out the root cause.
I know where the plugin increase that value
https://github.com/octokit/plugin-throttling.js/blob/c9c3bf68c663889e36389d170746db6dfe86cbb8/src/index.ts#L168-L172
options
comes from
https://github.com/octokit/plugin-throttling.js/blob/c9c3bf68c663889e36389d170746db6dfe86cbb8/src/index.ts#L110-L111
info
comes from that call on Bottleneck Job (eventInfo
)
retry = (await this.Events.trigger("failed", error, eventInfo));
which is a function arg
async _onFailure(error, eventInfo, clearGlobalState, run, free) {
which comes from that call (at least from what I saw during debug session)
return this._onFailure(error, eventInfo, clearGlobalState, run, free);
and is built there
eventInfo = {args: this.args, options: this.options, retryCount: this.retryCount};
this.arg
is a class argument
Job = class Job {
constructor(task, args, options, jobDefaults, rejectOnDrop, Events, _states, Promise) {
this.task = task;
this.args = args;
and seems (start to be hard to follow things from there :|) to come from there or there
In both cases, I don't understand at all how one job instance may impact another job instance args 🤔
Except if for a reason or another, request
is overridden somewhere during the process.
I will see if I can create a minimal snippet which reproduces the issue and will post it there (it will depend of time required, I already spent many time and can't afford to spend too much work day on that :/)
How to reproduce the issue :
-
index.js
const { throttling } = require('@octokit/plugin-throttling'); const { Octokit } = require('@octokit/rest'); const repoOwner = 'XXXXX'; const repoName = 'YYYYY'; const beforeRequestHook = async (options) => { console.log( { // Sometimes url already contains the base, sometimes not :| call: `${options.method} ${options.baseUrl}${options.url.replace(options.baseUrl, '')}`, }, 'Octokit - request will be sent', ); }; const afterRequestHook = async (options) => { // FYI: Will be called only if request did not throw an error (or if error was caught) console.log({ status: options.status, url: options.url }, 'Octokit - request has been sent'); }; const CustomOctokit = Octokit.plugin(throttling); const api = new CustomOctokit({ // auth: 'token', throttle: { onRateLimit: (retryAfter, options) => { const context = { method: options.method, url: options.url, retryCount: options.request.retryCount, retryAfter }; console.warn(context, `Throttle plugin error : Rate limit`); if (options.request.retryCount < 1) { // only retries once console.log(context, `Retrying request !`); return true; } console.error(context, `Request already retried, giving up`); return null; }, onAbuseLimit: (retryAfter, options) => { console.warn(context, `Throttle plugin error : Abuse`); return null; }, }, }); api.hook.before('request', beforeRequestHook); api.hook.after('request', afterRequestHook); async function doApiCall(ref) { try { // Will always fail as filename is random ! return await api.request(`GET /repos/${repoOwner}/${repoName}/contents/${ref}-${Math.random().toString(36).slice(2)}`) } catch(e) { if (!e.status || e.status !== 404) { throw e; } return null; } } async function doParallelApiCalls(ref) { return Promise.all([doApiCall(ref), doApiCall(ref), doApiCall(ref)]); } const asyncIterable = { [Symbol.asyncIterator]() { return { i: 0, async next() { if (this.i < 100) { this.i++; return Promise.resolve({ value: await doParallelApiCalls(this.i), done: false }); } return Promise.resolve({ done: true }); } }; } }; (async function() { for await (let num of asyncIterable) { console.log(num); } })();
-
package.json
:{ "dependencies": { "@octokit/plugin-throttling": "^3.5.1", "@octokit/rest": "^18.0.9", "bottleneck": "^2.19.5" } }
Then
-
npm install
-
node index.js
Issue seems to be totally random, you have to execute node index.js
many times, sometimes it works without issue, and sometimes it fails.
Working example output :
{ call:
'GET https://api.github.com/repos/XXXXX/YYYYY/contents/1-sa7x94o7b4q' } 'Octokit - request will be sent'
{ call:
'GET https://api.github.com/repos/XXXXX/YYYYY/contents/1-pzamkghgus' } 'Octokit - request will be sent'
{ call:
'GET https://api.github.com/repos/XXXXX/YYYYY/contents/1-uu1srtxc5xi' } 'Octokit - request will be sent'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-uu1srtxc5xi',
retryCount: 0, <--- OK !
retryAfter: 1867 } 'Throttle plugin error : Rate limit'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-uu1srtxc5xi',
retryCount: 0, <--- OK !
retryAfter: 1867 } 'Retrying request !'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-pzamkghgus',
retryCount: 0, <--- OK !
retryAfter: 1867 } 'Throttle plugin error : Rate limit'
{ method: 'GET', <--- OK !
url: '/repos/XXXXX/YYYYY/contents/1-pzamkghgus',
retryCount: 0,
retryAfter: 1867 } 'Retrying request !'
{ method: 'GET', <--- OK !
url: '/repos/XXXXX/YYYYY/contents/1-sa7x94o7b4q',
retryCount: 0, <--- OK !
retryAfter: 1867 } 'Throttle plugin error : Rate limit'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-sa7x94o7b4q',
retryCount: 0, <--- OK !
retryAfter: 1867 } 'Retrying request !'}
..... Process waits correctly until retryAfter is elapsed
Failing example output:
{ call:
'GET https://api.github.com/repos/XXXXX/YYYYY/contents/1-2zyky93cmm7' } 'Octokit - request will be sent'
{ call:
'GET https://api.github.com/repos/XXXXX/YYYYY/contents/1-mlapmrmt36' } 'Octokit - request will be sent'
{ call:
'GET https://api.github.com/repos/XXXXX/YYYYY/contents/1-6cuv8zce0pw' } 'Octokit - request will be sent'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-6cuv8zce0pw',
retryCount: 0, <--- OK !
retryAfter: 1839 } 'Throttle plugin error : Rate limit'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-6cuv8zce0pw',
retryCount: 0, <--- OK !
retryAfter: 1839 } 'Retrying request !'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-2zyky93cmm7',
retryCount: 1, <--- Argh !
retryAfter: 1839 } 'Throttle plugin error : Rate limit'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-2zyky93cmm7',
retryCount: 1, <--- Argh !
retryAfter: 1839 } 'Request already retried, giving up'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-mlapmrmt36',
retryCount: 1, <--- Argh !
retryAfter: 1839 } 'Throttle plugin error : Rate limit'
{ method: 'GET',
url: '/repos/XXXXX/YYYYY/contents/1-mlapmrmt36',
retryCount: 1, <--- Argh !
retryAfter: 1839 } 'Request already retried, giving up'
(node:25114) UnhandledPromiseRejectionWarning: HttpError: API rate limit exceeded for [REDACTED]. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
at fetch.then ([REDACTED]/node_modules/@octokit/request/dist-node/index.js:86:21)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:25114) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:25114) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Thanks a lot for the thorough research!
I'm fully occupied with https://github.com/octokit/octokit-next.js right now and for the foreseeable time, but I'll get back to it eventually. I don't have an idea what the problem might be out of head unfortunately.
Maybe @SGrondin who created Bottleneck and the initial version of this plugin might have ideas how to further debug the problem, but Simon moved on from Bottleneck I think, he has not been responsive recently.
No problem 👍 For the moment, we keep the plugin active knowing that it may not work in some cases. At least it delays requests which is a good thing anyway (in worst case, behavior will be the same than without the plugin, so let takes advantage of the other functionalities 😀 ).
BTW, thanks for your work 👌
:tada: This issue has been resolved in version 4.3.2 :tada:
The release is available on:
Your semantic-release bot :package::rocket: