Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 7 additions & 35 deletions server/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import cookieParser from "cookie-parser"
import authRouter from "./services/auth/routes/authRouter.js";
import clientRouter from './services/client/routes/clientRoutes.js';
import ingestRouter from "./services/ingest/routes/ingestRoutes.js"
import ShutdownManager from './shared/config/shutdown.js';

/**
* Initialize Express app
Expand Down Expand Up @@ -138,44 +139,15 @@ async function startServer() {
});


const gracefulShutdown = async (signal) => {
logger.info(`${signal} received, shutting down gracefully...`);
// Initialize shutdown manager
const shutdownManager = new ShutdownManager(logger);

server.close(async () => {
logger.info("HTTP server closed");
shutdownManager.register("MongoDB", () => mongodb.disconnect());
shutdownManager.register("PostgreSQL", () => postgres.close());
shutdownManager.register("RabbitMQ", () => rabbitmq.close());

try {
await mongodb.disconnect();
await postgres.close();
await rabbitmq.close();
logger.info('All connections closed, exiting process');
process.exit(0);
} catch (error) {
logger.error('Error during shutdown:', error);
process.exit(1);
}
})
shutdownManager.init(server);

setTimeout(() => {
logger.error("Forced shutdown")
process.exit(1);
}, 10000);

}

process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));

// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception:', error);
gracefulShutdown('uncaughtException');
});

process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
gracefulShutdown('unhandledRejection');
});

} catch (error) {
logger.error('Failed to start server:', error);
Expand Down
110 changes: 110 additions & 0 deletions server/src/shared/config/shutdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* ShutdownManager
* ----------------
* Centralized lifecycle manager for graceful shutdown of the application.
*
* Responsibilities:
* - Listen to system signals (SIGINT, SIGTERM)
* - Execute registered cleanup tasks in order
* - Ensure graceful shutdown with timeout fallback
* - Handle uncaught exceptions and unhandled rejections
*
* Usage:
* @example
* const shutdownManager = new ShutdownManager(logger);
* shutdownManager.register("mongodb", () => mongodb.disconnect());
* shutdownManager.init(server);
*/

export default class ShutdownManager {
constructor(logger, options = {}) {
this.logger = logger;
this.tasks = [];

this.timeout = options.timeout || 10000;
this.isShuttingDown = false;
}

/**
* Register a cleanup task
* @param {string} name - Name of the resource
* @param {Function} handler - Async cleanup function
*/
register(name, handler) {
this.tasks.push({ name, handler });
}

/**
* Execute all cleanup tasks
*/
async executeTasks() {
this.logger.info("Executing shutdown tasks...");

for (const task of this.tasks) {
try {
this.logger.info(`Closing: ${task.name}`);
await task.handler();
this.logger.info(`${task.name} closed successfully`);
} catch (error) {
this.logger.error(`Error closing ${task.name}:`, error);
}
}
}

/**
* Graceful shutdown handler
*/
async shutdown(signal, server) {
if (this.isShuttingDown) return;
this.isShuttingDown = true;

this.logger.info(`${signal} received. Starting graceful shutdown...`);

// Force shutdown fallback
const forceTimeout = setTimeout(() => {
this.logger.error("Forced shutdown triggered");
process.exit(1);
}, this.timeout);

try {
// Stop accepting new connections
if (server) {
await new Promise((resolve) => {
server.close(() => {
this.logger.info("HTTP server closed");
resolve();
});
});
}

// Run cleanup tasks
await this.executeTasks();

clearTimeout(forceTimeout);
this.logger.info("Shutdown completed successfully");
process.exit(0);

} catch (error) {
this.logger.error("Shutdown failed:", error);
process.exit(1);
}
}

/**
* Initialize listeners
*/
init(server) {
process.on("SIGINT", () => this.shutdown("SIGINT", server));
process.on("SIGTERM", () => this.shutdown("SIGTERM", server));

process.on("uncaughtException", (error) => {
this.logger.error("Uncaught Exception:", error);
this.shutdown("uncaughtException", server);
});

process.on("unhandledRejection", (reason, promise) => {
this.logger.error("Unhandled Rejection:", { reason, promise });
this.shutdown("unhandledRejection", server);
});
}
}