Swadhin Biswas Feature/ai code reviewer (#80) · 8/17/2026
9740d3d
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
/**
* Graceful shutdown handler for the main OpenCodeHub process.
* Import this at the top of the entry point or via NODE_OPTIONS --require.
*
* Handles SIGTERM, SIGINT, and uncaught errors to ensure:
* - In-flight HTTP requests complete
* - Database connections drain
* - Redis connections close
* - Worker queues stop accepting new jobs
*/
import { closeDatabase } from "@/db";
import { logger } from "@/lib/logger";
let isShuttingDown = false;
const SHUTDOWN_TIMEOUT_MS = 30_000;
async function gracefulShutdown(signal: string) {
if (isShuttingDown) return;
isShuttingDown = true;
logger.info({ signal }, "Received shutdown signal, draining...");
// Set flag so health checks return "shutting down"
process.env.OPCODEHUB_SHUTTING_DOWN = "1";
// Force exit after timeout
const forceTimer = setTimeout(() => {
logger.error("Forced shutdown after timeout");
process.exit(1);
}, SHUTDOWN_TIMEOUT_MS);
forceTimer.unref();
try {
// Close database connections
await closeDatabase();
logger.info("Database connections closed");
} catch (err) {
logger.error({ err }, "Error closing database connections");
}
try {
// Close Redis if available — import dynamically to avoid circular deps
const { closeRedis } = await import("@/lib/redis");
await closeRedis();
} catch {
// Redis not available, skip
}
logger.info("Shutdown complete, exiting...");
// Let the process exit naturally once all handles are drained
setTimeout(() => process.exit(0), 1000);
}
// Handle signals
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
// Handle uncaught errors — log and exit to let process manager restart
process.on("uncaughtException", (err) => {
logger.fatal({ err }, "Uncaught exception — exiting to prevent corrupted state");
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
logger.fatal({ reason }, "Unhandled rejection — exiting to let process manager restart");
process.exit(1);
});
// Memory pressure warning
if (process.env.NODE_ENV === "production") {
const memCheck = setInterval(() => {
const usage = process.memoryUsage();
const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
const rssUsedMB = Math.round(usage.rss / 1024 / 1024);
if (heapUsedMB > 1024) {
logger.warn({ heapUsedMB, rssUsedMB }, "High memory usage detected");
}
}, 60_000);
memCheck.unref();
}
export { isShuttingDown };