mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 06:22:04 +00:00
fix: memory leak in session removal - close MCP server properly (#471)
- 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: Claude <noreply@anthropic.com>
This commit is contained in:
25
CHANGELOG.md
25
CHANGELOG.md
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.28.7] - 2025-12-05
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
**Memory Leak: MCP Server Cleanup on Session Removal (#471)**
|
||||||
|
|
||||||
|
Fixed memory leak where `N8NDocumentationMCPServer` objects were not properly closed when sessions were removed, causing memory growth over time.
|
||||||
|
|
||||||
|
- **Root Cause**: `removeSession()` deleted server from map but didn't call cleanup methods
|
||||||
|
- **Evidence**: Production server memory grew from ~10% to ~35% in 43 minutes (~1GB growth with 4GB container)
|
||||||
|
|
||||||
|
- **Solution**:
|
||||||
|
- Added `close()` method to `N8NDocumentationMCPServer` that:
|
||||||
|
- Calls `server.close()` (MCP SDK cleanup)
|
||||||
|
- Calls `cache.destroy()` to stop cleanup timer and clear entries
|
||||||
|
- Closes database connection properly
|
||||||
|
- Nullifies service references to help GC
|
||||||
|
- Updated `removeSession()` to call `server.close()` before releasing references
|
||||||
|
|
||||||
|
- **Files Changed**:
|
||||||
|
- `src/mcp/server.ts` - Added `close()` method
|
||||||
|
- `src/http-server-single-session.ts` - Call `server.close()` in `removeSession()`
|
||||||
|
|
||||||
|
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
|
||||||
|
|
||||||
## [2.28.6] - 2025-12-05
|
## [2.28.6] - 2025-12-05
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
2
dist/http-server-single-session.d.ts.map
vendored
2
dist/http-server-single-session.d.ts.map
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"http-server-single-session.d.ts","sourceRoot":"","sources":["../src/http-server-single-session.ts"],"names":[],"mappings":";AAMA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoB9B,OAAO,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAuErD,qBAAa,uBAAuB;IAElC,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,eAAe,CAAsE;IAC7F,OAAO,CAAC,eAAe,CAA4D;IACnF,OAAO,CAAC,kBAAkB,CAAyC;IACnE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IA2B3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAwBpB,oBAAoB;IAwBlC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,mBAAmB;IAoDrB,aAAa,CACjB,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,IAAI,CAAC;YAmOF,eAAe;IA8C7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD/B,cAAc,IAAI;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE;YACT,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,GAAG,EAAE,MAAM,CAAC;YACZ,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH;IAmDM,kBAAkB,IAAI,YAAY,EAAE;IAoEpC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM;CAsG7D"}
|
{"version":3,"file":"http-server-single-session.d.ts","sourceRoot":"","sources":["../src/http-server-single-session.ts"],"names":[],"mappings":";AAMA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoB9B,OAAO,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAuErD,qBAAa,uBAAuB;IAElC,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,eAAe,CAAsE;IAC7F,OAAO,CAAC,eAAe,CAA4D;IACnF,OAAO,CAAC,kBAAkB,CAAyC;IACnE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IAkC3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAwBpB,oBAAoB;IAwBlC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,mBAAmB;IAoDrB,aAAa,CACjB,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,IAAI,CAAC;YAmOF,eAAe;IA8C7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD/B,cAAc,IAAI;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE;YACT,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,GAAG,EAAE,MAAM,CAAC;YACZ,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH;IAmDM,kBAAkB,IAAI,YAAY,EAAE;IAoEpC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM;CAsG7D"}
|
||||||
4
dist/http-server-single-session.js
vendored
4
dist/http-server-single-session.js
vendored
@@ -101,10 +101,14 @@ class SingleSessionHTTPServer {
|
|||||||
async removeSession(sessionId, reason) {
|
async removeSession(sessionId, reason) {
|
||||||
try {
|
try {
|
||||||
const transport = this.transports[sessionId];
|
const transport = this.transports[sessionId];
|
||||||
|
const server = this.servers[sessionId];
|
||||||
delete this.transports[sessionId];
|
delete this.transports[sessionId];
|
||||||
delete this.servers[sessionId];
|
delete this.servers[sessionId];
|
||||||
delete this.sessionMetadata[sessionId];
|
delete this.sessionMetadata[sessionId];
|
||||||
delete this.sessionContexts[sessionId];
|
delete this.sessionContexts[sessionId];
|
||||||
|
if (server) {
|
||||||
|
await server.close();
|
||||||
|
}
|
||||||
if (transport) {
|
if (transport) {
|
||||||
await transport.close();
|
await transport.close();
|
||||||
}
|
}
|
||||||
|
|||||||
2
dist/http-server-single-session.js.map
vendored
2
dist/http-server-single-session.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/mcp/server.d.ts
vendored
1
dist/mcp/server.d.ts
vendored
@@ -14,6 +14,7 @@ export declare class N8NDocumentationMCPServer {
|
|||||||
private earlyLogger;
|
private earlyLogger;
|
||||||
private disabledToolsCache;
|
private disabledToolsCache;
|
||||||
constructor(instanceContext?: InstanceContext, earlyLogger?: EarlyErrorLogger);
|
constructor(instanceContext?: InstanceContext, earlyLogger?: EarlyErrorLogger);
|
||||||
|
close(): Promise<void>;
|
||||||
private initializeDatabase;
|
private initializeDatabase;
|
||||||
private initializeInMemorySchema;
|
private initializeInMemorySchema;
|
||||||
private parseSQLStatements;
|
private parseSQLStatements;
|
||||||
|
|||||||
2
dist/mcp/server.d.ts.map
vendored
2
dist/mcp/server.d.ts.map
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAkFnE,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,qBAAqB,CAAsB;IACnD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,kBAAkB,CAA4B;gBAE1C,eAAe,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,gBAAgB;YAuF/D,kBAAkB;YAwClB,wBAAwB;IA0BtC,OAAO,CAAC,kBAAkB;YA6CZ,iBAAiB;IAa/B,OAAO,CAAC,eAAe,CAAkB;YAE3B,sBAAsB;IAgDpC,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,aAAa;IAoTrB,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,kBAAkB;IAqE1B,OAAO,CAAC,uBAAuB;IAwB/B,OAAO,CAAC,qBAAqB;YAgTf,SAAS;YA2DT,WAAW;YA0EX,WAAW;YAyCX,cAAc;YAyKd,gBAAgB;IAqD9B,OAAO,CAAC,mBAAmB;IAwE3B,OAAO,CAAC,eAAe;YAsBT,eAAe;IAqI7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,uBAAuB;IA0D/B,OAAO,CAAC,iBAAiB;YAqFX,WAAW;YAgCX,oBAAoB;YA2EpB,qBAAqB;YAwDrB,iBAAiB;YA2JjB,OAAO;YAgDP,cAAc;YAgFd,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,4BAA4B;YAKtB,oBAAoB;IAsDlC,OAAO,CAAC,gBAAgB;YAiBV,SAAS;YA6CT,kBAAkB;YA+DlB,uBAAuB;YAsDvB,iBAAiB;IAqE/B,OAAO,CAAC,qBAAqB;IA8C7B,OAAO,CAAC,uBAAuB;IAwD/B,OAAO,CAAC,iBAAiB;YAoDX,mBAAmB;YAkGnB,qBAAqB;IAS7B,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAS9B,aAAa;YAcb,iBAAiB;YAoBjB,WAAW;YAwBX,eAAe;YAqBf,mBAAmB;YAwBnB,yBAAyB;IA4CvC,OAAO,CAAC,kBAAkB;YAiBZ,gBAAgB;YA6HhB,2BAA2B;YAiE3B,2BAA2B;IAyEnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}
|
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAkFnE,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,qBAAqB,CAAsB;IACnD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,kBAAkB,CAA4B;gBAE1C,eAAe,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,gBAAgB;IA2FvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAed,kBAAkB;YAwClB,wBAAwB;IA0BtC,OAAO,CAAC,kBAAkB;YA6CZ,iBAAiB;IAa/B,OAAO,CAAC,eAAe,CAAkB;YAE3B,sBAAsB;IAgDpC,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,aAAa;IAoTrB,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,kBAAkB;IAqE1B,OAAO,CAAC,uBAAuB;IAwB/B,OAAO,CAAC,qBAAqB;YAgTf,SAAS;YA2DT,WAAW;YA0EX,WAAW;YAyCX,cAAc;YAyKd,gBAAgB;IAqD9B,OAAO,CAAC,mBAAmB;IAwE3B,OAAO,CAAC,eAAe;YAsBT,eAAe;IAqI7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,uBAAuB;IA0D/B,OAAO,CAAC,iBAAiB;YAqFX,WAAW;YAgCX,oBAAoB;YA2EpB,qBAAqB;YAwDrB,iBAAiB;YA2JjB,OAAO;YAgDP,cAAc;YAgFd,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,4BAA4B;YAKtB,oBAAoB;IAsDlC,OAAO,CAAC,gBAAgB;YAiBV,SAAS;YA6CT,kBAAkB;YA+DlB,uBAAuB;YAsDvB,iBAAiB;IAqE/B,OAAO,CAAC,qBAAqB;IA8C7B,OAAO,CAAC,uBAAuB;IAwD/B,OAAO,CAAC,iBAAiB;YAoDX,mBAAmB;YAkGnB,qBAAqB;IAS7B,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAS9B,aAAa;YAcb,iBAAiB;YAoBjB,WAAW;YAwBX,eAAe;YAqBf,mBAAmB;YAwBnB,yBAAyB;IA4CvC,OAAO,CAAC,kBAAkB;YAiBZ,gBAAgB;YA6HhB,2BAA2B;YAiE3B,2BAA2B;IAyEnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}
|
||||||
13
dist/mcp/server.js
vendored
13
dist/mcp/server.js
vendored
@@ -147,6 +147,19 @@ class N8NDocumentationMCPServer {
|
|||||||
});
|
});
|
||||||
this.setupHandlers();
|
this.setupHandlers();
|
||||||
}
|
}
|
||||||
|
async close() {
|
||||||
|
try {
|
||||||
|
await this.server.close();
|
||||||
|
this.cache.clear();
|
||||||
|
this.db = null;
|
||||||
|
this.repository = null;
|
||||||
|
this.templateService = null;
|
||||||
|
this.earlyLogger = null;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
logger_1.logger.warn('Error closing MCP server', { error: error instanceof Error ? error.message : String(error) });
|
||||||
|
}
|
||||||
|
}
|
||||||
async initializeDatabase(dbPath) {
|
async initializeDatabase(dbPath) {
|
||||||
try {
|
try {
|
||||||
if (this.earlyLogger) {
|
if (this.earlyLogger) {
|
||||||
|
|||||||
2
dist/mcp/server.js.map
vendored
2
dist/mcp/server.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.28.6",
|
"version": "2.28.7",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp-runtime",
|
"name": "n8n-mcp-runtime",
|
||||||
"version": "2.28.0",
|
"version": "2.28.7",
|
||||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -180,17 +180,29 @@ export class SingleSessionHTTPServer {
|
|||||||
*/
|
*/
|
||||||
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Store reference to transport before deletion
|
// Store references before deletion
|
||||||
const transport = this.transports[sessionId];
|
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
|
// This breaks the circular reference: removeSession -> close -> onclose -> removeSession
|
||||||
delete this.transports[sessionId];
|
delete this.transports[sessionId];
|
||||||
delete this.servers[sessionId];
|
delete this.servers[sessionId];
|
||||||
delete this.sessionMetadata[sessionId];
|
delete this.sessionMetadata[sessionId];
|
||||||
delete this.sessionContexts[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
|
// When onclose handler fires, it won't find the transport anymore
|
||||||
if (transport) {
|
if (transport) {
|
||||||
await transport.close();
|
await transport.close();
|
||||||
|
|||||||
@@ -220,7 +220,46 @@ export class N8NDocumentationMCPServer {
|
|||||||
|
|
||||||
this.setupHandlers();
|
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> {
|
private async initializeDatabase(dbPath: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Checkpoint: Database connecting (v2.18.3)
|
// Checkpoint: Database connecting (v2.18.3)
|
||||||
|
|||||||
Reference in New Issue
Block a user