mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-20 17:33:08 +00:00
- Add close() method to N8NDocumentationMCPServer that: - Calls server.close() (MCP SDK cleanup) - Clears internal cache - Nullifies service references to help GC - Update removeSession() to call server.close() before releasing references Root cause: removeSession() deleted server from map but didn't call cleanup Evidence: Production server memory grew ~1GB in 43 minutes (10% to 35%) Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
60479e0eb4
commit
cdaa29e7a2
@@ -180,17 +180,29 @@ export class SingleSessionHTTPServer {
|
||||
*/
|
||||
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
||||
try {
|
||||
// Store reference to transport before deletion
|
||||
// Store references before deletion
|
||||
const transport = this.transports[sessionId];
|
||||
const server = this.servers[sessionId];
|
||||
|
||||
// Delete transport FIRST to prevent onclose handler from triggering recursion
|
||||
// Delete references FIRST to prevent onclose handler from triggering recursion
|
||||
// This breaks the circular reference: removeSession -> close -> onclose -> removeSession
|
||||
delete this.transports[sessionId];
|
||||
delete this.servers[sessionId];
|
||||
delete this.sessionMetadata[sessionId];
|
||||
delete this.sessionContexts[sessionId];
|
||||
|
||||
// Close transport AFTER deletion
|
||||
// Close server first (may have references to transport)
|
||||
// This fixes memory leak where server resources weren't freed (issue #471)
|
||||
// Handle server close errors separately so transport close still runs
|
||||
if (server && typeof server.close === 'function') {
|
||||
try {
|
||||
await server.close();
|
||||
} catch (serverError) {
|
||||
logger.warn('Error closing server', { sessionId, error: serverError });
|
||||
}
|
||||
}
|
||||
|
||||
// Close transport last
|
||||
// When onclose handler fires, it won't find the transport anymore
|
||||
if (transport) {
|
||||
await transport.close();
|
||||
|
||||
@@ -220,7 +220,46 @@ export class N8NDocumentationMCPServer {
|
||||
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close the server and release resources.
|
||||
* Should be called when the session is being removed.
|
||||
*
|
||||
* Order of cleanup:
|
||||
* 1. Close MCP server connection
|
||||
* 2. Destroy cache (clears entries AND stops cleanup timer)
|
||||
* 3. Close database connection
|
||||
* 4. Null out references to help GC
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
try {
|
||||
await this.server.close();
|
||||
|
||||
// Use destroy() not clear() - also stops the cleanup timer
|
||||
this.cache.destroy();
|
||||
|
||||
// Close database connection before nullifying reference
|
||||
if (this.db) {
|
||||
try {
|
||||
this.db.close();
|
||||
} catch (dbError) {
|
||||
logger.warn('Error closing database', {
|
||||
error: dbError instanceof Error ? dbError.message : String(dbError)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Null out references to help garbage collection
|
||||
this.db = null;
|
||||
this.repository = null;
|
||||
this.templateService = null;
|
||||
this.earlyLogger = null;
|
||||
} catch (error) {
|
||||
// Log but don't throw - cleanup should be best-effort
|
||||
logger.warn('Error closing MCP server', { error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeDatabase(dbPath: string): Promise<void> {
|
||||
try {
|
||||
// Checkpoint: Database connecting (v2.18.3)
|
||||
|
||||
Reference in New Issue
Block a user