fix: prevent chroma-mcp process leaks on worker restart
Critical bugfix: ChromaSync now properly cleans up chroma-mcp subprocesses when the worker is restarted, preventing memory exhaustion from orphaned processes accumulating over time. Changes: - Store reference to StdioClientTransport subprocess - Explicitly close transport in close() method to kill subprocess - Add error handling to ensure cleanup even on failures - Reset all state in finally block Problem: Each worker restart spawned a new chroma-mcp process but never killed the old one. After multiple restarts, orphaned processes accumulated (16+ seen in production), consuming 900MB+ RAM and eventually causing OOM kills that silently failed backfills. Impact: - Eliminates process accumulation - Prevents memory exhaustion from leaked subprocesses - Fixes silent backfill failures caused by OOM kills - Ensures graceful cleanup on worker shutdown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -73,6 +73,7 @@ interface StoredUserPrompt {
|
|||||||
|
|
||||||
export class ChromaSync {
|
export class ChromaSync {
|
||||||
private client: Client | null = null;
|
private client: Client | null = null;
|
||||||
|
private transport: StdioClientTransport | null = null;
|
||||||
private connected: boolean = false;
|
private connected: boolean = false;
|
||||||
private project: string;
|
private project: string;
|
||||||
private collectionName: string;
|
private collectionName: string;
|
||||||
@@ -101,7 +102,7 @@ export class ChromaSync {
|
|||||||
// See: https://github.com/thedotmack/claude-mem/issues/170 (Python 3.14 incompatibility)
|
// See: https://github.com/thedotmack/claude-mem/issues/170 (Python 3.14 incompatibility)
|
||||||
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
|
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
|
||||||
const pythonVersion = settings.CLAUDE_MEM_PYTHON_VERSION;
|
const pythonVersion = settings.CLAUDE_MEM_PYTHON_VERSION;
|
||||||
const transport = new StdioClientTransport({
|
this.transport = new StdioClientTransport({
|
||||||
command: 'uvx',
|
command: 'uvx',
|
||||||
args: [
|
args: [
|
||||||
'--python', pythonVersion,
|
'--python', pythonVersion,
|
||||||
@@ -119,7 +120,7 @@ export class ChromaSync {
|
|||||||
capabilities: {}
|
capabilities: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.client.connect(transport);
|
await this.client.connect(this.transport);
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
|
|
||||||
logger.info('CHROMA_SYNC', 'Connected to Chroma MCP server', { project: this.project });
|
logger.info('CHROMA_SYNC', 'Connected to Chroma MCP server', { project: this.project });
|
||||||
@@ -815,14 +816,38 @@ export class ChromaSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the Chroma client connection
|
* Close the Chroma client connection and cleanup subprocess
|
||||||
*/
|
*/
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
if (this.client && this.connected) {
|
if (!this.connected && !this.client && !this.transport) {
|
||||||
await this.client.close();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Close client first
|
||||||
|
if (this.client) {
|
||||||
|
try {
|
||||||
|
await this.client.close();
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('CHROMA_SYNC', 'Error closing Chroma client', { project: this.project }, error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly close transport to kill subprocess
|
||||||
|
if (this.transport) {
|
||||||
|
try {
|
||||||
|
await this.transport.close();
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('CHROMA_SYNC', 'Error closing transport', { project: this.project }, error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('CHROMA_SYNC', 'Chroma client and subprocess closed', { project: this.project });
|
||||||
|
} finally {
|
||||||
|
// Always reset state, even if errors occurred
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
this.client = null;
|
this.client = null;
|
||||||
logger.info('CHROMA_SYNC', 'Chroma client closed', { project: this.project });
|
this.transport = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user