From d923f7127ac72b84c3234159f0fec09de1ba196d Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Thu, 24 Nov 2022 14:01:09 -0500 Subject: [PATCH 1/3] fix #2205 - reject commands in connect phase when `disableOfflineQueue` is `true` (#2328) --- packages/client/lib/client/index.spec.ts | 18 +++++++++++++++++- packages/client/lib/client/index.ts | 10 +++++----- packages/client/lib/errors.ts | 6 ++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index 6294e155a4..4eee707629 100644 --- a/packages/client/lib/client/index.spec.ts +++ b/packages/client/lib/client/index.spec.ts @@ -3,7 +3,7 @@ import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils'; import RedisClient, { RedisClientType } from '.'; import { RedisClientMultiCommandType } from './multi-command'; import { RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands'; -import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors'; +import { AbortError, ClientClosedError, ClientOfflineError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors'; import { defineScript } from '../lua-script'; import { spy } from 'sinon'; import { once } from 'events'; @@ -874,4 +874,20 @@ describe('Client', () => { pingInterval: 1 } }); + + testUtils.testWithClient('should reject commands in connect phase when `disableOfflineQueue`', async client => { + const connectPromise = client.connect(); + await assert.rejects( + client.ping(), + ClientOfflineError + ); + await connectPromise; + await client.disconnect(); + }, { + ...GLOBAL.SERVERS.OPEN, + clientOptions: { + disableOfflineQueue: true + }, + disableClientSetup: true + }); }); diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index e6f1fef10e..c4259f72b8 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -11,7 +11,7 @@ import { ScanCommandOptions } from '../commands/SCAN'; import { HScanTuple } from '../commands/HSCAN'; import { attachCommands, attachExtensions, fCallArguments, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander'; import { Pool, Options as PoolOptions, createPool } from 'generic-pool'; -import { ClientClosedError, DisconnectsClientError } from '../errors'; +import { ClientClosedError, ClientOfflineError, DisconnectsClientError } from '../errors'; import { URL } from 'url'; import { TcpSocketConnectOpts } from 'net'; @@ -405,16 +405,16 @@ export default class RedisClient< ): Promise { if (!this.#socket.isOpen) { return Promise.reject(new ClientClosedError()); - } - - if (options?.isolated) { + } else if (options?.isolated) { return this.executeIsolated(isolatedClient => isolatedClient.sendCommand(args, { ...options, isolated: false }) ); - } + } else if (!this.#socket.isReady && this.#options?.disableOfflineQueue) { + return Promise.reject(new ClientOfflineError()); + } const promise = this.#queue.addCommand(args, options); this.#tick(); diff --git a/packages/client/lib/errors.ts b/packages/client/lib/errors.ts index 3f3b962498..3070970315 100644 --- a/packages/client/lib/errors.ts +++ b/packages/client/lib/errors.ts @@ -22,6 +22,12 @@ export class ClientClosedError extends Error { } } +export class ClientOfflineError extends Error { + constructor() { + super('The client is offline'); + } +} + export class DisconnectsClientError extends Error { constructor() { super('Disconnects client'); From 28b9701543b548ad0520ac4dcda7f057caa08491 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Thu, 24 Nov 2022 14:01:43 -0500 Subject: [PATCH 2/3] fix #2318 - add MULTI (uppercase) (#2324) --- packages/client/lib/client/index.ts | 4 +++- packages/client/lib/cluster/index.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index c4259f72b8..e1ddb64b9a 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -619,13 +619,15 @@ export default class RedisClient< return this.#isolationPool.use(fn); } - multi(): RedisClientMultiCommandType { + MULTI(): RedisClientMultiCommandType { return new (this as any).Multi( this.multiExecutor.bind(this), this.#options?.legacyMode ); } + multi = this.MULTI; + async multiExecutor( commands: Array, selectedDB?: number, diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index 57ec6ff705..6eafdda86c 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -224,7 +224,7 @@ export default class RedisCluster< } } - multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType { + MULTI(routing?: RedisCommandArgument): RedisClusterMultiCommandType { return new this.#Multi( (commands: Array, firstKey?: RedisCommandArgument, chainId?: symbol) => { return this.#execute( @@ -237,6 +237,8 @@ export default class RedisCluster< ); } + multi = this.MULTI; + getMasters(): Array> { return this.#slots.getMasters(); } From 13ad249ae626f76bb5d2ec7fe02b5dcfd37b150f Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Thu, 24 Nov 2022 14:03:34 -0500 Subject: [PATCH 3/3] fix #2010 - stop reconnect after .disconnect() (#2323) * fix #2010 - stop reconnect after .disconnect() * fix quit --- packages/client/lib/client/socket.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/client/lib/client/socket.ts b/packages/client/lib/client/socket.ts index fabc22038d..cc9d04c7b2 100644 --- a/packages/client/lib/client/socket.ts +++ b/packages/client/lib/client/socket.ts @@ -105,6 +105,7 @@ export default class RedisSocket extends EventEmitter { throw new Error('Socket already opened'); } + this.#isOpen = true; return this.#connect(); } @@ -116,7 +117,6 @@ export default class RedisSocket extends EventEmitter { } try { - this.#isOpen = true; this.#socket = await this.#createSocket(); this.#writableNeedDrain = false; this.emit('connect'); @@ -142,7 +142,7 @@ export default class RedisSocket extends EventEmitter { await promiseTimeout(retryIn); } retries++; - } while (!this.#isReady); + } while (this.#isOpen && !this.#isReady); } #createSocket(): Promise { @@ -203,6 +203,8 @@ export default class RedisSocket extends EventEmitter { this.#isReady = false; this.emit('error', err); + if (!this.#isOpen) return; + this.#connect(true).catch(() => { // the error was already emitted, silently ignore it }); @@ -219,14 +221,22 @@ export default class RedisSocket extends EventEmitter { } disconnect(): void { - if (!this.#socket) { + if (!this.#isOpen) { throw new ClientClosedError(); - } else { - this.#isOpen = this.#isReady = false; } - this.#socket.destroy(); - this.#socket = undefined; + this.#isOpen = false; + this.#disconnect(); + } + + #disconnect(): void { + this.#isReady = false; + + if (this.#socket) { + this.#socket.destroy(); + this.#socket = undefined; + } + this.emit('end'); } @@ -237,7 +247,7 @@ export default class RedisSocket extends EventEmitter { this.#isOpen = false; await fn(); - this.disconnect(); + this.#disconnect(); } #isCorked = false;