Fix memory leaks from orphaned uvx/python processes (#120)

This fixes memory leak, will remove one unnecessary MCP after this in a new PR but this is mission critical fix

* Initial plan

* Fix memory leaks: Add proper cleanup for ChromaSync and search server processes

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Add comprehensive process cleanup and PM2 configuration improvements

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Add comprehensive summary and recommendations for memory leak fixes

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
This commit is contained in:
Copilot
2025-11-16 22:16:41 -05:00
committed by GitHub
parent 60d5f8fbf1
commit c46e4a341a
9 changed files with 620 additions and 25 deletions
+41
View File
@@ -1740,6 +1740,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}
});
// Cleanup function to properly terminate all child processes
async function cleanup() {
console.error('[search-server] Shutting down...');
// Close Chroma client (terminates uvx/python processes)
if (chromaClient) {
try {
await chromaClient.close();
console.error('[search-server] Chroma client closed');
} catch (error: any) {
console.error('[search-server] Error closing Chroma client:', error.message);
}
}
// Close database connections
if (search) {
try {
search.close();
console.error('[search-server] SessionSearch closed');
} catch (error: any) {
console.error('[search-server] Error closing SessionSearch:', error.message);
}
}
if (store) {
try {
store.close();
console.error('[search-server] SessionStore closed');
} catch (error: any) {
console.error('[search-server] Error closing SessionStore:', error.message);
}
}
console.error('[search-server] Shutdown complete');
process.exit(0);
}
// Register cleanup handlers for graceful shutdown
process.on('SIGTERM', cleanup);
process.on('SIGINT', cleanup);
// Start the server
async function main() {
// Start the MCP server FIRST (critical - must start before blocking operations)
+11 -1
View File
@@ -215,6 +215,16 @@ export class WorkerService {
// Shutdown all active sessions
await this.sessionManager.shutdownAll();
// Close MCP client connection (terminates search server process)
if (this.mcpClient) {
try {
await this.mcpClient.close();
logger.info('SYSTEM', 'MCP client closed');
} catch (error) {
logger.error('SYSTEM', 'Failed to close MCP client', {}, error as Error);
}
}
// Close HTTP server
if (this.server) {
await new Promise<void>((resolve, reject) => {
@@ -222,7 +232,7 @@ export class WorkerService {
});
}
// Close database connection
// Close database connection (includes ChromaSync cleanup)
await this.dbManager.close();
logger.info('SYSTEM', 'Worker shutdown complete');
+15 -3
View File
@@ -30,16 +30,28 @@ export class DatabaseManager {
// Initialize ChromaSync
this.chromaSync = new ChromaSync('claude-mem');
// Start background backfill (fire-and-forget)
this.chromaSync.ensureBackfilled().catch(() => {});
// Start background backfill (fire-and-forget, with error logging)
this.chromaSync.ensureBackfilled().catch((error) => {
logger.error('DB', 'Chroma backfill failed (non-fatal)', {}, error);
});
logger.info('DB', 'Database initialized');
}
/**
* Close database connection
* Close database connection and cleanup all resources
*/
async close(): Promise<void> {
// Close ChromaSync first (terminates uvx/python processes)
if (this.chromaSync) {
try {
await this.chromaSync.close();
this.chromaSync = null;
} catch (error) {
logger.error('DB', 'Failed to close ChromaSync', {}, error as Error);
}
}
if (this.sessionStore) {
this.sessionStore.close();
this.sessionStore = null;