-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat: Add command timeout #2981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
4ad6a4a
to
90f8fe9
Compare
90f8fe9
to
e8eb95b
Compare
Hi @nkaradzhov sorry for pinging you directly, but I couldn't find information on how to contact maintainers for this project. |
@florian-schunk hi, sorry for the delay, been a bit busy. I will take a look at this. |
let controller: AbortController; | ||
if (this._self.#options?.commandTimeout) { | ||
controller = new AbortController() | ||
let abortSignal = controller.signal; | ||
if (options?.abortSignal) { | ||
abortSignal = AbortSignal.any([ | ||
abortSignal, | ||
options.abortSignal | ||
]); | ||
} | ||
options = { | ||
...options, | ||
abortSignal | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify this by using the AbortSignal.timeout static method:
if (this._self.#options?.commandTimeout) {
let abortSignal = AbortSignal.timeout(
this._self.#options?.commandTimeout
);
if (options?.abortSignal) {
abortSignal = AbortSignal.any([abortSignal, options.abortSignal]);
}
options = {
...options,
abortSignal
};
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this, also together with the change in the test that you proposed, but somehow for me the test keeps failing with this approach. I am not quite understanding, why.
packages/client/lib/client/index.ts
Outdated
|
||
this._self.#scheduleWrite(); | ||
return promise; | ||
if (!this._self.#options?.commandTimeout) { | ||
return promise; | ||
} | ||
|
||
return new Promise<T>((resolve, reject) => { | ||
const timeoutId = setTimeout(() => { | ||
controller.abort(); | ||
reject(new CommandTimeoutError()); | ||
}, this._self.#options?.commandTimeout) | ||
promise.then(result => { | ||
clearInterval(timeoutId); | ||
resolve(result) | ||
}).catch(error => { | ||
clearInterval(timeoutId); | ||
reject(error) | ||
}); | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then, this change becomes obsolete
packages/client/lib/errors.ts
Outdated
export class CommandTimeoutError extends Error { | ||
constructor() { | ||
super('Command timeout'); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to introduce another error, we can use the AbortError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I changed it to use AbortError, but couldn't there be situation where it makes sense to distinguish between a timeout and having the command manually aborted? Wouldn't it then make more sense to have a different error?
testUtils.testWithClient('CommandTimeoutError', async client => { | ||
const promise = assert.rejects(client.sendCommand(['PING']), CommandTimeoutError), | ||
start = process.hrtime.bigint(); | ||
|
||
while (process.hrtime.bigint() - start < 50_000_000) { | ||
// block the event loop for 1ms, to make sure the connection will timeout | ||
} | ||
|
||
await promise; | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
commandTimeout: 50, | ||
} | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test would need to change like this:
testUtils.testWithClient('CommandTimeoutError', async client => {
const promise = client.sendCommand(['PING'])
const start = process.hrtime.bigint();
while (process.hrtime.bigint() - start < 50_000_000) {
// block the event loop for 50ms, to make sure the connection will timeout
}
assert.rejects(promise, AbortError);
}, {
...GLOBAL.SERVERS.OPEN,
clientOptions: {
commandTimeout: 50,
}
});
I will take a look, maybe will make a pr to your pr
…On Fri, Jun 20, 2025, 6:16 PM florian-schunk ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In packages/client/lib/client/index.ts
<#2981 (comment)>:
> + let controller: AbortController;
+ if (this._self.#options?.commandTimeout) {
+ controller = new AbortController()
+ let abortSignal = controller.signal;
+ if (options?.abortSignal) {
+ abortSignal = AbortSignal.any([
+ abortSignal,
+ options.abortSignal
+ ]);
+ }
+ options = {
+ ...options,
+ abortSignal
+ }
+ }
I tried this, also together with the change in the test that you proposed,
but somehow for me the test keeps failing with this approach. I am not
quite understanding, why.
—
Reply to this email directly, view it on GitHub
<#2981 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AALIHLDULWEBI6J3YYY37E33EQQUVAVCNFSM6AAAAAB6CNSAFSVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDSNBWGU2DKMRQGE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Description
This PR introduces a timeout for commands sent to redis.
Our use-case is that we are using node-rate-limiter-flexible with node-redis, but sometimes the connection to redis is slow. In these cases we want to fall back the the insurance limiter of node-rate-limiter-flexible.
This feature was also requested in #2175
Checklist
npm test
pass with this change (including linting)?