Fix MongoDB connection leak with singleton pattern and auto-reconnection#136
Fix MongoDB connection leak with singleton pattern and auto-reconnection#136
Conversation
- Implement singleton pattern for MongoDB client - Reuse single connection across all requests - Add proper connection lifecycle management - Initialize connection on startup - Close connection gracefully on shutdown Co-authored-by: Apollon77 <11976694+Apollon77@users.noreply.github.com>
- Fix race condition in connectDatabase() with connection promise - Implement lazy connection initialization on first use - Fix graceful shutdown to properly await server.close() - Remove eager connection on startup for better error handling Co-authored-by: Apollon77 <11976694+Apollon77@users.noreply.github.com>
- Clean up connectionPromise after it's fulfilled in connectDatabase() - Add try-catch error handling to closeDatabaseConnection() - Ensure state is always reset even if close() throws an error Co-authored-by: Apollon77 <11976694+Apollon77@users.noreply.github.com>
- Change connectDatabase() to return Db instead of Promise<void> - Remove global db variable, use only client - Change connectionPromise to Promise<Db> - Eliminate need for type assertions (db!) in dbConnect() - Simplify code with one less global variable Co-authored-by: Apollon77 <11976694+Apollon77@users.noreply.github.com>
Implemented in commit 18f3e6c. The |
- Listen for 'close' event on MongoDB client - Automatically clear client and connectionPromise on disconnect - Next dbConnect() call will create new connection automatically - Also add 'error' event listener for better error visibility Co-authored-by: Apollon77 <11976694+Apollon77@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes MongoDB connection leaks by implementing a singleton pattern for database connections, adds graceful shutdown handlers, and introduces automatic reconnection on disconnect detection.
Changes:
- Implemented singleton pattern in
db/utils.tsto reuse a single MongoDB client across all requests instead of creating new clients for each query - Added graceful shutdown handlers in
index.tsfor SIGTERM and SIGINT signals to properly close HTTP server and database connections - Added auto-reconnection logic with MongoDB event listeners to detect and recover from unexpected disconnections
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| express/backend/src/db/utils.ts | Implements singleton pattern with module-level client variable, connection promise for race condition prevention, auto-reconnection via event listeners, and proper cleanup functions |
| express/backend/src/index.ts | Adds graceful shutdown handlers for SIGTERM/SIGINT signals that close the HTTP server and database connection before exiting |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
express/backend/src/db/utils.ts
Outdated
| await client.connect(); | ||
|
|
||
| // Set up event listeners to detect disconnections | ||
| client.on("close", () => { | ||
| console.log("MongoDB connection closed unexpectedly"); | ||
| client = null; | ||
| connectionPromise = null; | ||
| }); | ||
|
|
||
| client.on("error", (error) => { | ||
| console.error("MongoDB client error:", error); | ||
| }); | ||
|
|
||
| const db = client.db(); | ||
| console.log("MongoDB connected successfully"); | ||
| return db; |
There was a problem hiding this comment.
If the connection attempt fails (e.g., MongoDB is unreachable), the error will be thrown and propagated to the caller, but the client variable may be left in an inconsistent state. The client will be set to a new MongoClient instance at line 27, but if client.connect() fails at line 28, the client variable will still reference the failed client object. Subsequent calls to connectDatabase() will then pass the check at line 17 and return client.db() for a client that never successfully connected, which will likely fail.
Add error handling in the connection promise to ensure that if the connection fails, the client variable is reset to null so that the next call to connectDatabase() will attempt a fresh connection.
| await client.connect(); | |
| // Set up event listeners to detect disconnections | |
| client.on("close", () => { | |
| console.log("MongoDB connection closed unexpectedly"); | |
| client = null; | |
| connectionPromise = null; | |
| }); | |
| client.on("error", (error) => { | |
| console.error("MongoDB client error:", error); | |
| }); | |
| const db = client.db(); | |
| console.log("MongoDB connected successfully"); | |
| return db; | |
| try { | |
| await client.connect(); | |
| // Set up event listeners to detect disconnections | |
| client.on("close", () => { | |
| console.log("MongoDB connection closed unexpectedly"); | |
| client = null; | |
| connectionPromise = null; | |
| }); | |
| client.on("error", (error) => { | |
| console.error("MongoDB client error:", error); | |
| }); | |
| const db = client.db(); | |
| console.log("MongoDB connected successfully"); | |
| return db; | |
| } catch (error) { | |
| console.error("Error connecting to MongoDB:", error); | |
| if (client) { | |
| try { | |
| await client.close(); | |
| } catch (closeError) { | |
| console.error("Error closing MongoDB client after failed connection:", closeError); | |
| } | |
| } | |
| client = null; | |
| throw error; | |
| } |
There was a problem hiding this comment.
Verified and fixed in commit 55a4a43. Added try-catch around client.connect() to handle connection failures. If connection fails, the client is properly cleaned up (closed and set to null) to prevent inconsistent state. The error is then re-thrown to propagate to the caller.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Wrap client.connect() in try-catch to handle connection failures - Clean up client on connection failure to prevent inconsistent state - Add 30-second timeout to graceful shutdown to prevent hanging - Use Promise.race to ensure shutdown completes within timeout Co-authored-by: Apollon77 <11976694+Apollon77@users.noreply.github.com>
UncleSamSwiss
left a comment
There was a problem hiding this comment.
Thanks for the PR, please find my comments below. Feel free to change things if you agree, or leave it as-is.
| let client: MongoClient | null = null; | ||
| let connectionPromise: Promise<Db> | null = null; | ||
|
|
||
| export function createClient() { |
There was a problem hiding this comment.
As nobody should create DB clients directly, we shouldn't export this function.
| return new MongoClient("mongodb://mongo/dev-portal"); | ||
| } | ||
|
|
||
| export async function connectDatabase(): Promise<Db> { |
There was a problem hiding this comment.
We might want to remove the export here because everybody should use dbConnect() instead. But leaving this function exported doesn't hurt either.
| startCronJobs(); | ||
|
|
||
| // Graceful shutdown helper | ||
| async function gracefulShutdown(signal: string) { |
There was a problem hiding this comment.
I'm not sure if we really need graceful shutdown handling as none of these resources are persistent beyond the process lifetime.
MongoDB Connection Leak Fix - Complete ✅
Analysis Complete
dbConnect()creates new clients without closing themImplementation Complete
dbConnect()to reuse a single client connectionFinal Solution
db/utils.ts - Connection Management:
index.ts - Application Lifecycle:
Benefits Delivered
✅ No Connection Leaks - Single connection reused across all requests
✅ Lazy Initialization - Database connects only when needed
✅ Better Error Handling - Server starts even if MongoDB temporarily unavailable
✅ Connection Failure Recovery - Cleans up failed connections properly
✅ Race Condition Prevention - Concurrent connection attempts use same promise
✅ Graceful Shutdown - Properly closes server and database connections with timeout
✅ Robust Cleanup - Connection promise cleaned up after use, error handling on close
✅ Cleaner Code - One less global variable, no type assertions needed
✅ Auto-Reconnection - Detects disconnections and automatically reconnects on next use
✅ Memory Leak Prevention - Event listeners properly cleaned up
✅ Security - No vulnerabilities introduced
Recent Updates
Technical Details
dbConnect()created new MongoClient, never closed → connection leaksTesting Status
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.