1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

Fix typo and improve Sentinel docs (#2931)

This commit is contained in:
Bobby I.
2025-04-30 16:30:16 +03:00
committed by GitHub
parent 49d6b2d465
commit 46bfeaa94e
4 changed files with 118 additions and 36 deletions

View File

@@ -134,7 +134,7 @@ describe(`test with scripts`, () => {
}, GLOBAL.SENTINEL.WITH_SCRIPT);
testUtils.testWithClientSentinel('with script multi', async sentinel => {
const reply = await sentinel.multi().set('key', 2).square('key').exec();
const reply = await sentinel.multi().set('key', 2).square('key').exec();
assert.deepEqual(reply, ['OK', 4]);
}, GLOBAL.SENTINEL.WITH_SCRIPT);
@@ -148,7 +148,7 @@ describe(`test with scripts`, () => {
);
}, GLOBAL.SENTINEL.WITH_SCRIPT)
});
describe(`test with functions`, () => {
testUtils.testWithClientSentinel('with function', async sentinel => {
@@ -178,14 +178,14 @@ describe(`test with functions`, () => {
MATH_FUNCTION.code,
{ REPLACE: true }
);
const reply = await sentinel.use(
async (client: any) => {
await client.set('key', '2');
return client.math.square('key');
}
);
assert.equal(reply, 4);
}, GLOBAL.SENTINEL.WITH_FUNCTION);
});
@@ -216,7 +216,7 @@ describe(`test with replica pool size 1`, () => {
testUtils.testWithClientSentinel('client lease', async sentinel => {
sentinel.on("error", () => { });
const clientLease = await sentinel.aquire();
const clientLease = await sentinel.acquire();
clientLease.set('x', 456);
let matched = false;
@@ -243,7 +243,7 @@ describe(`test with replica pool size 1`, () => {
return await client.get("x");
}
)
await sentinel.set("x", 1);
assert.equal(await promise, null);
}, GLOBAL.SENTINEL.WITH_REPLICA_POOL_SIZE_1);
@@ -276,7 +276,7 @@ describe(`test with masterPoolSize 2, reserve client true`, () => {
assert.equal(await promise2, "2");
}, Object.assign(GLOBAL.SENTINEL.WITH_RESERVE_CLIENT_MASTER_POOL_SIZE_2, {skipTest: true}));
});
describe(`test with masterPoolSize 2`, () => {
testUtils.testWithClientSentinel('multple clients', async sentinel => {
sentinel.on("error", () => { });
@@ -313,14 +313,14 @@ describe(`test with masterPoolSize 2`, () => {
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
testUtils.testWithClientSentinel('lease - watch - clean', async sentinel => {
const leasedClient = await sentinel.aquire();
const leasedClient = await sentinel.acquire();
await leasedClient.set('x', 1);
await leasedClient.watch('x');
assert.deepEqual(await leasedClient.multi().get('x').exec(), ['1'])
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
testUtils.testWithClientSentinel('lease - watch - dirty', async sentinel => {
const leasedClient = await sentinel.aquire();
const leasedClient = await sentinel.acquire();
await leasedClient.set('x', 1);
await leasedClient.watch('x');
await leasedClient.set('x', 2);
@@ -328,11 +328,11 @@ describe(`test with masterPoolSize 2`, () => {
await assert.rejects(leasedClient.multi().get('x').exec(), new WatchError());
}, GLOBAL.SENTINEL.WITH_MASTER_POOL_SIZE_2);
});
// TODO: Figure out how to modify the test utils
// so it would have fine grained controll over
// sentinel
// sentinel
// it should somehow replicate the `SentinelFramework` object functionallities
async function steadyState(frame: SentinelFramework) {
let checkedMaster = false;
@@ -439,7 +439,7 @@ describe.skip('legacy tests', () => {
sentinel.on('error', () => { });
}
if (this!.currentTest!.state === 'failed') {
if (this!.currentTest!.state === 'failed') {
console.log(`longest event loop blocked delta: ${longestDelta}`);
console.log(`longest event loop blocked in failing test: ${longestTestDelta}`);
console.log("trace:");
@@ -454,7 +454,7 @@ describe.skip('legacy tests', () => {
frame.sentinelMaster(),
frame.sentinelReplicas()
])
console.log(`sentinel sentinels:\n${JSON.stringify(results[0], undefined, '\t')}`);
console.log(`sentinel master:\n${JSON.stringify(results[1], undefined, '\t')}`);
console.log(`sentinel replicas:\n${JSON.stringify(results[2], undefined, '\t')}`);
@@ -492,7 +492,7 @@ describe.skip('legacy tests', () => {
);
});
// stops master to force sentinel to update
// stops master to force sentinel to update
it('stop master', async function () {
this.timeout(60000);
@@ -538,8 +538,8 @@ describe.skip('legacy tests', () => {
tracer.push("connected");
const client = await sentinel.aquire();
tracer.push("aquired lease");
const client = await sentinel.acquire();
tracer.push("acquired lease");
await client.set("x", 1);
await client.watch("x");
@@ -586,7 +586,7 @@ describe.skip('legacy tests', () => {
await sentinel.connect();
tracer.push("connected");
const client = await sentinel.aquire();
const client = await sentinel.acquire();
tracer.push("got leased client");
await client.set("x", 1);
await client.watch("x");
@@ -965,10 +965,10 @@ describe.skip('legacy tests', () => {
tracer.push("adding node");
await frame.addNode();
tracer.push("added node and waiting on added promise");
await nodeAddedPromise;
await nodeAddedPromise;
})
});
});

View File

@@ -32,14 +32,30 @@ export class RedisSentinelClient<
#internal: RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>;
readonly _self: RedisSentinelClient<M, F, S, RESP, TYPE_MAPPING>;
/**
* Indicates if the client connection is open
*
* @returns `true` if the client connection is open, `false` otherwise
*/
get isOpen() {
return this._self.#internal.isOpen;
}
/**
* Indicates if the client connection is ready to accept commands
*
* @returns `true` if the client connection is ready, `false` otherwise
*/
get isReady() {
return this._self.#internal.isReady;
}
/**
* Gets the command options configured for this client
*
* @returns The command options for this client or `undefined` if none were set
*/
get commandOptions() {
return this._self.#commandOptions;
}
@@ -222,6 +238,16 @@ export class RedisSentinelClient<
unwatch = this.UNWATCH;
/**
* Releases the client lease back to the pool
*
* After calling this method, the client instance should no longer be used as it
* will be returned to the client pool and may be given to other operations.
*
* @returns A promise that resolves when the client is ready to be reused, or undefined
* if the client was immediately ready
* @throws Error if the lease has already been released
*/
release() {
if (this._self.#clientInfo === undefined) {
throw new Error('RedisSentinelClient lease already released');
@@ -245,10 +271,20 @@ export default class RedisSentinel<
#internal: RedisSentinelInternal<M, F, S, RESP, TYPE_MAPPING>;
#options: RedisSentinelOptions<M, F, S, RESP, TYPE_MAPPING>;
/**
* Indicates if the sentinel connection is open
*
* @returns `true` if the sentinel connection is open, `false` otherwise
*/
get isOpen() {
return this._self.#internal.isOpen;
}
/**
* Indicates if the sentinel connection is ready to accept commands
*
* @returns `true` if the sentinel connection is ready, `false` otherwise
*/
get isReady() {
return this._self.#internal.isReady;
}
@@ -511,7 +547,28 @@ export default class RedisSentinel<
pUnsubscribe = this.PUNSUBSCRIBE;
async aquire(): Promise<RedisSentinelClientType<M, F, S, RESP, TYPE_MAPPING>> {
/**
* Acquires a master client lease for exclusive operations
*
* Used when multiple commands need to run on an exclusive client (for example, using `WATCH/MULTI/EXEC`).
* The returned client must be released after use with the `release()` method.
*
* @returns A promise that resolves to a Redis client connected to the master node
* @example
* ```javascript
* const clientLease = await sentinel.acquire();
*
* try {
* await clientLease.watch('key');
* const resp = await clientLease.multi()
* .get('key')
* .exec();
* } finally {
* clientLease.release();
* }
* ```
*/
async acquire(): Promise<RedisSentinelClientType<M, F, S, RESP, TYPE_MAPPING>> {
const clientInfo = await this._self.#internal.getClientLease();
return RedisSentinelClient.create(this._self.#options, this._self.#internal, clientInfo, this._self.#commandOptions);
}
@@ -641,6 +698,12 @@ class RedisSentinelInternal<
});
}
/**
* Gets a client lease from the master client pool
*
* @returns A client info object or a promise that resolves to a client info object
* when a client becomes available
*/
getClientLease(): ClientInfo | Promise<ClientInfo> {
const id = this.#masterClientQueue.shift();
if (id !== undefined) {
@@ -650,6 +713,16 @@ class RedisSentinelInternal<
return this.#masterClientQueue.wait().then(id => ({ id }));
}
/**
* Releases a client lease back to the pool
*
* If the client was used for a transaction that might have left it in a dirty state,
* it will be reset before being returned to the pool.
*
* @param clientInfo The client info object representing the client to release
* @returns A promise that resolves when the client is ready to be reused, or undefined
* if the client was immediately ready or no longer exists
*/
releaseClientLease(clientInfo: ClientInfo) {
const client = this.#masterClients[clientInfo.id];
// client can be undefined if releasing in middle of a reconfigure

View File

@@ -49,11 +49,17 @@ export interface RedisSentinelOptions<
*/
replicaPoolSize?: number;
/**
* TODO
* Interval in milliseconds to periodically scan for changes in the sentinel topology.
* The client will query the sentinel for changes at this interval.
*
* Default: 10000 (10 seconds)
*/
scanInterval?: number;
/**
* TODO
* When `true`, error events from client instances inside the sentinel will be propagated to the sentinel instance.
* This allows handling all client errors through a single error handler on the sentinel instance.
*
* Default: false
*/
passthroughClientErrorEvents?: boolean;
/**