redis-smq-common
Version:
Provides essential components and utilities shared across RedisSMQ packages.
136 lines • 4.58 kB
JavaScript
import { spawn } from 'child_process';
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { env } from '../env/index.js';
import { net } from '../net/index.js';
import { PowerSwitch } from '../power-switch/index.js';
import { constants } from './constants.js';
import { RedisServerBinaryNotFoundError, RedisServerStartupFailedError, } from './errors/index.js';
const { REDIS_BINARY_PATH } = constants;
const execAsync = promisify(exec);
export class RedisServer {
process = null;
port = null;
powerSwitch = new PowerSwitch();
constructor() {
this.bindExitHandler();
}
bindExitHandler() {
process.once('beforeExit', () => this.shutdown());
}
async findRedisBinary() {
try {
const command = process.platform === 'win32'
? 'where redis-server'
: 'which redis-server';
const { stdout } = await execAsync(command);
const path = stdout.trim();
if (path)
return path;
}
catch {
}
if (await env.doesPathExist(REDIS_BINARY_PATH)) {
return REDIS_BINARY_PATH;
}
throw new RedisServerBinaryNotFoundError();
}
createRedisArgs(port) {
return ['--port', port.toString(), '--save', '', '--appendonly', 'no'];
}
waitForReady(timeoutMs) {
return new Promise((resolve, reject) => {
if (!this.process) {
reject(new Error('Redis process not started'));
return;
}
const timeout = setTimeout(() => {
cleanup();
reject(new RedisServerStartupFailedError({
message: `Redis server start timeout after ${timeoutMs}ms`,
}));
}, timeoutMs);
const onData = (data) => {
if (data.toString().includes('Ready to accept connections')) {
cleanup();
resolve();
}
};
const onExit = () => {
cleanup();
};
const onError = (error) => {
cleanup();
reject(new RedisServerStartupFailedError({
message: `Redis process error: ${error.message}`,
}));
};
const cleanup = () => {
clearTimeout(timeout);
this.process?.stdout?.removeListener('data', onData);
this.process?.removeListener('exit', onExit);
this.process?.removeListener('error', onError);
};
this.process.stdout?.on('data', onData);
this.process.on('exit', onExit);
this.process.on('error', onError);
});
}
waitForShutdown(timeoutMs) {
return new Promise((resolve) => {
if (!this.process) {
resolve();
return;
}
const timeout = setTimeout(() => {
cleanup();
this.process?.kill('SIGKILL');
resolve();
}, timeoutMs);
const onExit = () => {
cleanup();
resolve();
};
const cleanup = () => {
clearTimeout(timeout);
this.process?.removeListener('exit', onExit);
};
this.process.once('exit', onExit);
this.process.kill('SIGTERM');
});
}
async start(requestedPort) {
if (!this.powerSwitch.goingUp()) {
throw new Error('Already started or going up');
}
const redisBinary = await this.findRedisBinary();
this.port = requestedPort || (await net.getRandomPort());
this.process = spawn(redisBinary, this.createRedisArgs(this.port));
try {
await this.waitForReady(10000);
}
catch (error) {
if (this.process && !this.process.exitCode && !this.process.killed) {
throw error;
}
return this.port;
}
this.powerSwitch.commit();
return this.port;
}
async shutdown() {
if (!this.powerSwitch.goingDown())
return;
try {
if (this.process) {
await this.waitForShutdown(5000);
}
}
finally {
this.process = null;
this.port = null;
this.powerSwitch.commit();
}
}
}
//# sourceMappingURL=redis-server.js.map