109 lines
2.6 KiB
TypeScript
109 lines
2.6 KiB
TypeScript
import { ch, db } from '@openpanel/db';
|
|
import {
|
|
cronQueue,
|
|
eventsGroupQueues,
|
|
miscQueue,
|
|
notificationQueue,
|
|
sessionsQueue,
|
|
} from '@openpanel/queue';
|
|
import {
|
|
getRedisCache,
|
|
getRedisPub,
|
|
getRedisQueue,
|
|
getRedisSub,
|
|
} from '@openpanel/redis';
|
|
import type { FastifyInstance } from 'fastify';
|
|
import { logger } from './logger';
|
|
|
|
let shuttingDown = false;
|
|
|
|
export function setShuttingDown(value: boolean) {
|
|
shuttingDown = value;
|
|
}
|
|
|
|
export function isShuttingDown() {
|
|
return shuttingDown;
|
|
}
|
|
|
|
// Graceful shutdown handler
|
|
export async function shutdown(
|
|
fastify: FastifyInstance,
|
|
signal: string,
|
|
exitCode = 0
|
|
) {
|
|
if (isShuttingDown()) {
|
|
logger.warn('Shutdown already in progress, ignoring signal', { signal });
|
|
return;
|
|
}
|
|
|
|
logger.info('Starting graceful shutdown', { signal });
|
|
|
|
setShuttingDown(true);
|
|
|
|
// Step 2: Wait for load balancer to stop sending traffic (matches preStop sleep)
|
|
const gracePeriod = Number(process.env.SHUTDOWN_GRACE_PERIOD_MS || '5000');
|
|
await new Promise((resolve) => setTimeout(resolve, gracePeriod));
|
|
|
|
// Step 3: Close Fastify to drain in-flight requests
|
|
try {
|
|
await fastify.close();
|
|
logger.info('Fastify server closed');
|
|
} catch (error) {
|
|
logger.error('Error closing Fastify server', error);
|
|
}
|
|
|
|
// Step 4: Close database connections
|
|
try {
|
|
await db.$disconnect();
|
|
logger.info('Database connection closed');
|
|
} catch (error) {
|
|
logger.error('Error closing database connection', error);
|
|
}
|
|
|
|
// Step 5: Close ClickHouse connections
|
|
try {
|
|
await ch.close();
|
|
logger.info('ClickHouse connections closed');
|
|
} catch (error) {
|
|
logger.error('Error closing ClickHouse connections', error);
|
|
}
|
|
|
|
// Step 6: Close Bull queues (graceful shutdown of queue state)
|
|
try {
|
|
await Promise.all([
|
|
...eventsGroupQueues.map((queue) => queue.close()),
|
|
sessionsQueue.close(),
|
|
cronQueue.close(),
|
|
miscQueue.close(),
|
|
notificationQueue.close(),
|
|
]);
|
|
logger.info('Queue state closed');
|
|
} catch (error) {
|
|
logger.error('Error closing queue state', error);
|
|
}
|
|
|
|
// Step 7: Close Redis connections
|
|
try {
|
|
const redisConnections = [
|
|
getRedisCache(),
|
|
getRedisPub(),
|
|
getRedisSub(),
|
|
getRedisQueue(),
|
|
];
|
|
|
|
await Promise.all(
|
|
redisConnections.map(async (redis) => {
|
|
if (redis.status === 'ready') {
|
|
await redis.quit();
|
|
}
|
|
})
|
|
);
|
|
logger.info('Redis connections closed');
|
|
} catch (error) {
|
|
logger.error('Error closing Redis connections', error);
|
|
}
|
|
|
|
logger.info('Graceful shutdown completed');
|
|
process.exit(exitCode);
|
|
}
|