forked from Openpanel-dev/openpanel
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgraceful-shutdown.ts
More file actions
122 lines (107 loc) · 3 KB
/
Copy pathgraceful-shutdown.ts
File metadata and controls
122 lines (107 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { ch, db } from '@openpanel/db';
import {
cronQueue,
disconnectKafka,
eventsGroupQueues,
miscQueue,
notificationQueue,
sessionsQueue,
} from '@openpanel/queue';
import {
getRedisCache,
getRedisEvent,
getRedisPub,
getRedisQueue,
getRedisSub,
getRedisSession,
} 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 6.5: Disconnect Kafka producer (no-op if never initialized).
// Flushes any in-flight produces so an API rollout doesn't drop events.
try {
await disconnectKafka();
logger.info('Kafka producer disconnected');
} catch (error) {
logger.error('Error disconnecting Kafka producer', error);
}
// Step 7: Close Redis connections
try {
const redisConnections = [
getRedisCache(),
getRedisPub(),
getRedisSub(),
getRedisQueue(),
getRedisSession(),
getRedisEvent(),
];
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);
}