You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-12-09 21:21:11 +03:00
* feat(errors): Add specialized timeout error types for maintenance scenarios - Added `SocketTimeoutDuringMaintananceError`, a subclass of `TimeoutError`, to handle socket timeouts during maintenance. - Added `CommandTimeoutDuringMaintenanceError`, another subclass of `TimeoutError`, to address command write timeouts during maintenance. * feat(linked-list): Add EmptyAwareSinglyLinkedList and enhance DoublyLinkedList functionality - Introduced `EmptyAwareSinglyLinkedList`, a subclass of `SinglyLinkedList` that emits an `empty` event when the list becomes empty due to `reset`, `shift`, or `remove` operations. - Added `nodes()` iterator method to `DoublyLinkedList` for iterating over nodes directly. - Enhanced unit tests for `DoublyLinkedList` and `SinglyLinkedList` to cover edge cases and new functionality. - Added comprehensive tests for `EmptyAwareSinglyLinkedList` to validate `empty` event emission under various scenarios. - Improved code formatting and consistency. * refactor(commands-queue): Improve push notification handling - Replaced `setInvalidateCallback` with a more flexible `addPushHandler` method, allowing multiple handlers for push notifications. - Introduced the `PushHandler` type to standardize push notification processing. - Refactored `RedisCommandsQueue` to use a `#pushHandlers` array, enabling dynamic and modular handling of push notifications. - Updated `RedisClient` to leverage the new handler mechanism for `invalidate` push notifications, simplifying and decoupling logic. * feat(commands-queue): Add method to wait for in-flight commands to complete - Introduced `waitForInflightCommandsToComplete` method to asynchronously wait for all in-flight commands to finish processing. - Utilized the `empty` event from `#waitingForReply` to signal when all commands have been completed. * feat(commands-queue): Introduce maintenance mode support for commands-queue - Added `#maintenanceCommandTimeout` and `setMaintenanceCommandTimeout` method to dynamically adjust command timeouts during maintenance * refator(client): Extract socket event listener setup into helper method * refactor(socket): Add maintenance mode support and dynamic timeout handling - Added `#maintenanceTimeout` and `setMaintenanceTimeout` method to dynamically adjust socket timeouts during maintenance. * feat(client): Add Redis Enterprise maintenance configuration options - Added `maintPushNotifications` option to control how the client handles Redis Enterprise maintenance push notifications (`disabled`, `enabled`, `au to`). - Added `maintMovingEndpointType` option to specify the endpoint type for reconnecting during a MOVING notification (`auto`, `internal-ip`, `external-ip`, etc.). - Added `maintRelaxedCommandTimeout` option to define a relaxed timeout for commands during maintenance. - Added `maintRelaxedSocketTimeout` option to define a relaxed timeout for the socket during maintenance. - Enforced RESP3 requirement for maintenance-related features (`maintPushNotifications`). * feat(client): Add socket helpers and pause mechanism - Introduced `#paused` flag with corresponding `_pause` and `_unpause` methods to temporarily halt writing commands to the socket during maintenance windows. - Updated `#write` method to respect the `#paused` flag, preventing new commands from being written during maintenance. - Added `_ejectSocket` method to safely detach from and return the current socket - Added `_insertSocket` method to receive and start using a new socket * feat(client): Add Redis Enterprise maintenance handling capabilities - Introduced `EnterpriseMaintenanceManager` to manage Redis Enterprise maintenance events and push notifications. - Integrated `EnterpriseMaintenanceManager` into `RedisClient` to handle maintenance push notifications and manage socket transitions. - Implemented graceful handling of MOVING, MIGRATING, and FAILOVER push notifications, including socket replacement and timeout adjustments. * test: add E2E test infrastructure for Redis maintenance scenarios * test: add E2E tests for Redis Enterprise maintenance timeout handling (#3) * test: add connection handoff test --------- Co-authored-by: Pavel Pashov <pavel.pashov@redis.com> Co-authored-by: Pavel Pashov <60297174+PavelPashov@users.noreply.github.com>
109 lines
2.9 KiB
TypeScript
109 lines
2.9 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { setTimeout } from "node:timers/promises";
|
|
import { createClient } from "../../..";
|
|
|
|
/**
|
|
* Options for the `fireCommandsUntilStopSignal` method
|
|
*/
|
|
type FireCommandsUntilStopSignalOptions = {
|
|
/**
|
|
* Number of commands to fire in each batch
|
|
*/
|
|
batchSize: number;
|
|
/**
|
|
* Timeout between batches in milliseconds
|
|
*/
|
|
timeoutMs: number;
|
|
/**
|
|
* Function that creates the commands to be executed
|
|
*/
|
|
createCommands: (
|
|
client: ReturnType<typeof createClient<any, any, any, any>>
|
|
) => Array<() => Promise<unknown>>;
|
|
};
|
|
|
|
/**
|
|
* Utility class for running test commands until a stop signal is received
|
|
*/
|
|
export class TestCommandRunner {
|
|
private static readonly defaultOptions: FireCommandsUntilStopSignalOptions = {
|
|
batchSize: 60,
|
|
timeoutMs: 10,
|
|
createCommands: (
|
|
client: ReturnType<typeof createClient<any, any, any, any>>
|
|
) => [
|
|
() => client.set(randomUUID(), Date.now()),
|
|
() => client.get(randomUUID()),
|
|
],
|
|
};
|
|
|
|
static #toSettled<T>(p: Promise<T>) {
|
|
return p
|
|
.then((value) => ({ status: "fulfilled" as const, value, error: null }))
|
|
.catch((reason) => ({
|
|
status: "rejected" as const,
|
|
value: null,
|
|
error: reason,
|
|
}));
|
|
}
|
|
|
|
static async #racePromises<S, T>({
|
|
timeout,
|
|
stopper,
|
|
}: {
|
|
timeout: Promise<S>;
|
|
stopper: Promise<T>;
|
|
}) {
|
|
return Promise.race([
|
|
TestCommandRunner.#toSettled<S>(timeout).then((result) => ({
|
|
...result,
|
|
stop: false,
|
|
})),
|
|
TestCommandRunner.#toSettled<T>(stopper).then((result) => ({
|
|
...result,
|
|
stop: true,
|
|
})),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Fires a batch of test commands until a stop signal is received
|
|
* @param client - The Redis client to use
|
|
* @param stopSignalPromise - Promise that resolves when the execution should stop
|
|
* @param options - Options for the command execution
|
|
* @returns An object containing the promises of all executed commands and the result of the stop signal
|
|
*/
|
|
static async fireCommandsUntilStopSignal(
|
|
client: ReturnType<typeof createClient<any, any, any, any>>,
|
|
stopSignalPromise: Promise<unknown>,
|
|
options?: Partial<FireCommandsUntilStopSignalOptions>
|
|
) {
|
|
const executeOptions = {
|
|
...TestCommandRunner.defaultOptions,
|
|
...options,
|
|
};
|
|
|
|
const commandPromises = [];
|
|
|
|
while (true) {
|
|
for (let i = 0; i < executeOptions.batchSize; i++) {
|
|
for (const command of executeOptions.createCommands(client)) {
|
|
commandPromises.push(TestCommandRunner.#toSettled(command()));
|
|
}
|
|
}
|
|
|
|
const result = await TestCommandRunner.#racePromises({
|
|
timeout: setTimeout(executeOptions.timeoutMs),
|
|
stopper: stopSignalPromise,
|
|
});
|
|
|
|
if (result.stop) {
|
|
return {
|
|
commandPromises,
|
|
stopResult: result,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|