diff --git a/docs/client-configuration.md b/docs/client-configuration.md index 6b7e7da7532..a67cef462ac 100644 --- a/docs/client-configuration.md +++ b/docs/client-configuration.md @@ -25,6 +25,7 @@ | readonly | `false` | Connect in [`READONLY`](https://redis.io/commands/readonly) mode | | legacyMode | `false` | Maintain some backwards compatibility (see the [Migration Guide](./v3-to-v4.md)) | | isolationPoolOptions | | See the [Isolated Execution Guide](./isolated-execution.md) | +| pingInterval | | Send `PING` command at interval (in ms). Useful with "[Azure Cache for Redis](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-best-practices-connection#idle-timeout)" | ## Reconnect Strategy diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index 27cb86d657e..6294e155a44 100644 --- a/packages/client/lib/client/index.spec.ts +++ b/packages/client/lib/client/index.spec.ts @@ -862,4 +862,16 @@ describe('Client', () => { client.unref(); client.ref(); }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('pingInterval', async client => { + assert.deepEqual( + await once(client, 'ping-interval'), + ['PONG'] + ); + }, { + ...GLOBAL.SERVERS.OPEN, + clientOptions: { + pingInterval: 1 + } + }); }); diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 52895c73c3e..e6f1fef10e5 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -31,6 +31,7 @@ export interface RedisClientOptions< readonly?: boolean; legacyMode?: boolean; isolationPoolOptions?: PoolOptions; + pingInterval?: number; } type WithCommands = { @@ -281,9 +282,12 @@ export default class RedisClient< this.#queue.flushAll(err); } }) - .on('connect', () => this.emit('connect')) + .on('connect', () => { + this.emit('connect'); + }) .on('ready', () => { this.emit('ready'); + this.#setPingTimer(); this.#tick(); }) .on('reconnecting', () => this.emit('reconnecting')) @@ -348,6 +352,22 @@ export default class RedisClient< (...args: Array): void => (this as any).sendCommand(name, ...args); } + #pingTimer?: NodeJS.Timer; + + #setPingTimer(): void { + if (!this.#options?.pingInterval || !this.#socket.isReady) return; + clearTimeout(this.#pingTimer); + + this.#pingTimer = setTimeout(() => { + if (!this.#socket.isReady) return; + + (this as unknown as RedisClientType).ping() + .then(reply => this.emit('ping-interval', reply)) + .catch(err => this.emit('error', err)) + .finally(() => this.#setPingTimer()); + }, this.#options.pingInterval); + } + duplicate(overrides?: Partial>): RedisClientType { return new (Object.getPrototypeOf(this).constructor)({ ...this.#options,