c46e4a341a
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>
124 lines
3.4 KiB
TypeScript
124 lines
3.4 KiB
TypeScript
/**
|
|
* DatabaseManager: Single long-lived database connection
|
|
*
|
|
* Responsibility:
|
|
* - Manage single database connection for worker lifetime
|
|
* - Provide centralized access to SessionStore and SessionSearch
|
|
* - High-level database operations
|
|
* - ChromaSync integration
|
|
*/
|
|
|
|
import { SessionStore } from '../sqlite/SessionStore.js';
|
|
import { SessionSearch } from '../sqlite/SessionSearch.js';
|
|
import { ChromaSync } from '../sync/ChromaSync.js';
|
|
import { logger } from '../../utils/logger.js';
|
|
import type { DBSession } from '../worker-types.js';
|
|
|
|
export class DatabaseManager {
|
|
private sessionStore: SessionStore | null = null;
|
|
private sessionSearch: SessionSearch | null = null;
|
|
private chromaSync: ChromaSync | null = null;
|
|
|
|
/**
|
|
* Initialize database connection (once, stays open)
|
|
*/
|
|
async initialize(): Promise<void> {
|
|
// Open database connection (ONCE)
|
|
this.sessionStore = new SessionStore();
|
|
this.sessionSearch = new SessionSearch();
|
|
|
|
// Initialize ChromaSync
|
|
this.chromaSync = new ChromaSync('claude-mem');
|
|
|
|
// 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 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;
|
|
}
|
|
if (this.sessionSearch) {
|
|
this.sessionSearch.close();
|
|
this.sessionSearch = null;
|
|
}
|
|
logger.info('DB', 'Database closed');
|
|
}
|
|
|
|
/**
|
|
* Get SessionStore instance (throws if not initialized)
|
|
*/
|
|
getSessionStore(): SessionStore {
|
|
if (!this.sessionStore) {
|
|
throw new Error('Database not initialized');
|
|
}
|
|
return this.sessionStore;
|
|
}
|
|
|
|
/**
|
|
* Get SessionSearch instance (throws if not initialized)
|
|
*/
|
|
getSessionSearch(): SessionSearch {
|
|
if (!this.sessionSearch) {
|
|
throw new Error('Database not initialized');
|
|
}
|
|
return this.sessionSearch;
|
|
}
|
|
|
|
/**
|
|
* Get ChromaSync instance (throws if not initialized)
|
|
*/
|
|
getChromaSync(): ChromaSync {
|
|
if (!this.chromaSync) {
|
|
throw new Error('ChromaSync not initialized');
|
|
}
|
|
return this.chromaSync;
|
|
}
|
|
|
|
// REMOVED: cleanupOrphanedSessions - violates "EVERYTHING SHOULD SAVE ALWAYS"
|
|
// Worker restarts don't make sessions orphaned. Sessions are managed by hooks
|
|
// and exist independently of worker state.
|
|
|
|
/**
|
|
* Get session by ID (throws if not found)
|
|
*/
|
|
getSessionById(sessionDbId: number): {
|
|
id: number;
|
|
claude_session_id: string;
|
|
sdk_session_id: string | null;
|
|
project: string;
|
|
user_prompt: string;
|
|
} {
|
|
const session = this.getSessionStore().getSessionById(sessionDbId);
|
|
if (!session) {
|
|
throw new Error(`Session ${sessionDbId} not found`);
|
|
}
|
|
return session;
|
|
}
|
|
|
|
/**
|
|
* Mark session as completed
|
|
*/
|
|
markSessionComplete(sessionDbId: number): void {
|
|
this.getSessionStore().markSessionCompleted(sessionDbId);
|
|
}
|
|
}
|