diff --git a/lib/Redis.ts b/lib/Redis.ts index ff7cf29b..18121574 100644 --- a/lib/Redis.ts +++ b/lib/Redis.ts @@ -106,6 +106,7 @@ class Redis extends Commander implements DataHandledable { private connectionEpoch = 0; private retryAttempts = 0; private manuallyClosing = false; + private socketTimeoutTimer: NodeJS.Timeout | undefined; // Prepare autopipelines structures private _autoPipelines = new Map(); @@ -523,6 +524,10 @@ class Redis extends Commander implements DataHandledable { if (Command.checkFlag("WILL_DISCONNECT", command.name)) { this.manuallyClosing = true; } + + if (this.options.socketTimeout !== undefined && this.socketTimeoutTimer === undefined) { + this.setSocketTimeout(); + } } if (command.name === "select" && isInt(command.args[0])) { @@ -537,6 +542,22 @@ class Redis extends Commander implements DataHandledable { return command.promise; } + private setSocketTimeout() { + this.socketTimeoutTimer = setTimeout(() => { + this.stream.destroy(new Error(`Socket timeout. Expecting data, but didn't receive any in ${this.options.socketTimeout}ms.`)); + this.socketTimeoutTimer = undefined; + }, this.options.socketTimeout); + + // this handler must run after the "data" handler in "DataHandler" + // so that `this.commandQueue.length` will be updated + this.stream.once("data", () => { + clearTimeout(this.socketTimeoutTimer); + this.socketTimeoutTimer = undefined; + if (this.commandQueue.length === 0) return; + this.setSocketTimeout(); + }); + } + scanStream(options?: ScanStreamOptions) { return this.createScanStream("scan", { options }); } diff --git a/lib/redis/RedisOptions.ts b/lib/redis/RedisOptions.ts index e580d191..26cc90aa 100644 --- a/lib/redis/RedisOptions.ts +++ b/lib/redis/RedisOptions.ts @@ -14,6 +14,15 @@ export interface CommonRedisOptions extends CommanderOptions { * a "Command timed out" error will be thrown. */ commandTimeout?: number; + + /** + * If the socket does not receive data within a set number of milliseconds: + * 1. the socket is considered "dead" and will be destroyed + * 2. the client will reject any running commands (altought they might have been processed by the server) + * 3. the reconnect strategy will kick in (depending on the configuration) + */ + socketTimeout?: number; + /** * Enable/disable keep-alive functionality. * @link https://nodejs.org/api/net.html#socketsetkeepaliveenable-initialdelay