mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 23:19:43 +02:00
fix: memory leak prevention and resource cleanup improvements
SSE Client Management: - Add periodic health check (30s) to detect dead SSE connections - Clean up clients with destroyed/non-writable sockets - Clear all SSE clients on server stop Session Lifecycle: - Add _isStopped flag to prevent new timers after session stops - Check flag in auto-compact/clear callbacks to avoid timer races - Check flag in line buffer flush timer creation Server Shutdown: - Add _isStopping flag to prevent timer creation during shutdown - Skip batch timer creation in terminal/output/task/state handlers - Clear SSE health check timer on stop Ralph Tracker: - Call clearDebounceTimers() in clear() method (was missing) - Add MAX_COMPLETION_PHRASE_ENTRIES (50) to trim phrase map - Add MAX_LINE_BUFFER_SIZE (64KB) to prevent unbounded growth - Trim line buffer when exceeding max size Task Tracker: - Add timestamp to pending tool uses for age-based cleanup - Add PENDING_TOOL_USE_MAX_AGE_MS (1 hour) expiry - Add MAX_PENDING_TOOL_USES (100) limit - Clean up old entries on new tool use Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,17 @@ const CLEANUP_THROTTLE_MS = 30 * 1000;
|
||||
*/
|
||||
const EVENT_DEBOUNCE_MS = 50;
|
||||
|
||||
/**
|
||||
* Maximum number of completion phrase entries to track.
|
||||
* Prevents unbounded growth if many unique phrases are seen.
|
||||
*/
|
||||
const MAX_COMPLETION_PHRASE_ENTRIES = 50;
|
||||
|
||||
/**
|
||||
* Maximum line buffer size to prevent unbounded growth from long lines.
|
||||
*/
|
||||
const MAX_LINE_BUFFER_SIZE = 64 * 1024;
|
||||
|
||||
// ========== Pre-compiled Regex Patterns ==========
|
||||
// Pre-compiled for performance (avoid re-compilation on each call)
|
||||
|
||||
@@ -546,6 +557,12 @@ export class RalphTracker extends EventEmitter {
|
||||
// Buffer data for line-based processing
|
||||
this._lineBuffer += cleanData;
|
||||
|
||||
// Prevent unbounded line buffer growth from very long lines
|
||||
if (this._lineBuffer.length > MAX_LINE_BUFFER_SIZE) {
|
||||
// Truncate to last portion to preserve recent data
|
||||
this._lineBuffer = this._lineBuffer.slice(-MAX_LINE_BUFFER_SIZE / 2);
|
||||
}
|
||||
|
||||
// Process complete lines
|
||||
const lines = this._lineBuffer.split('\n');
|
||||
this._lineBuffer = lines.pop() || ''; // Keep incomplete line in buffer
|
||||
@@ -870,6 +887,23 @@ export class RalphTracker extends EventEmitter {
|
||||
const count = (this._completionPhraseCount.get(phrase) || 0) + 1;
|
||||
this._completionPhraseCount.set(phrase, count);
|
||||
|
||||
// Trim completion phrase map if it exceeds the limit
|
||||
if (this._completionPhraseCount.size > MAX_COMPLETION_PHRASE_ENTRIES) {
|
||||
// Keep only the most important entries (current expected phrase and highest counts)
|
||||
const entries = Array.from(this._completionPhraseCount.entries());
|
||||
entries.sort((a, b) => b[1] - a[1]); // Sort by count descending
|
||||
this._completionPhraseCount.clear();
|
||||
// Keep top half of entries
|
||||
const keepCount = Math.floor(MAX_COMPLETION_PHRASE_ENTRIES / 2);
|
||||
for (let i = 0; i < Math.min(keepCount, entries.length); i++) {
|
||||
this._completionPhraseCount.set(entries[i][0], entries[i][1]);
|
||||
}
|
||||
// Always keep the expected phrase if set
|
||||
if (this._loopState.completionPhrase && !this._completionPhraseCount.has(this._loopState.completionPhrase)) {
|
||||
this._completionPhraseCount.set(this._loopState.completionPhrase, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Store phrase on first occurrence
|
||||
if (!this._loopState.completionPhrase) {
|
||||
this._loopState.completionPhrase = phrase;
|
||||
@@ -1340,6 +1374,8 @@ export class RalphTracker extends EventEmitter {
|
||||
* @fires todoUpdate - With empty array
|
||||
*/
|
||||
clear(): void {
|
||||
// Clear debounce timers to prevent stale emissions after clear
|
||||
this.clearDebounceTimers();
|
||||
this._loopState = createInitialRalphTrackerState(); // This sets enabled: false
|
||||
this._todos.clear();
|
||||
this._lineBuffer = '';
|
||||
|
||||
Reference in New Issue
Block a user