feat(02-01): add comprehensive dispose() to FileWatcher and DataCache

FileWatcher.dispose():
- Calls stop() to close watchers
- Clears all timers: retry, debounce, catch-up, polling
- Clears all tracking maps: lastProcessedLineCount, lastProcessedSize, activeSessionFiles, polledFileSizes, processingInProgress, pendingReprocess
- Calls removeAllListeners() LAST to prevent events during cleanup
- Sets disposed flag to prevent reuse

DataCache.dispose():
- Clears cache Map
- Disables caching
- Sets disposed flag to prevent reuse
- Auto-cleanup interval managed by caller (ServiceContext)

Both services prevent restart after disposal for proper lifecycle management.
This commit is contained in:
matt 2026-02-12 00:55:44 +00:00
parent 777d93f968
commit 767c985947
2 changed files with 95 additions and 0 deletions

View file

@ -29,6 +29,7 @@ export class DataCache {
private maxSize: number;
private ttl: number; // Time-to-live in milliseconds
private enabled: boolean; // Whether caching is enabled
private disposed = false; // Flag to prevent reuse after disposal
private static readonly CURRENT_VERSION = 2; // Increment when cache structure changes
constructor(maxSize: number = 50, ttlMinutes: number = 10, enabled: boolean = true) {
@ -353,4 +354,32 @@ export class DataCache {
return sessionIds;
}
/**
* Disposes the cache and prevents further use.
* Clears all cached data and disables caching.
*
* Note: The auto-cleanup interval returned by startAutoCleanup() is managed
* by the caller (ServiceContext), not stored internally, so we only need to
* clear the cache and disable it.
*/
dispose(): void {
if (this.disposed) {
logger.info('DataCache already disposed');
return;
}
logger.info('Disposing DataCache');
// Clear all cached data
this.cache.clear();
// Disable caching
this.enabled = false;
// Mark as disposed
this.disposed = true;
logger.info('DataCache disposed');
}
}

View file

@ -80,6 +80,8 @@ export class FileWatcher extends EventEmitter {
private processingInProgress = new Set<string>();
/** Files that need reprocessing after current processing completes */
private pendingReprocess = new Set<string>();
/** Flag to prevent reuse after disposal */
private disposed = false;
constructor(
dataCache: DataCache,
@ -117,6 +119,11 @@ export class FileWatcher extends EventEmitter {
* Starts watching the projects and todos directories.
*/
start(): void {
if (this.disposed) {
logger.error('Cannot start disposed FileWatcher');
return;
}
if (this.isWatching) {
logger.warn('Already watching');
return;
@ -181,6 +188,65 @@ export class FileWatcher extends EventEmitter {
logger.info('Stopped watching');
}
/**
* Disposes all resources and prevents reuse.
* Performs comprehensive cleanup of all timers, watchers, maps, and listeners.
*
* After calling dispose(), this FileWatcher cannot be restarted.
* Use stop() for temporary pausing that can be resumed with start().
*/
dispose(): void {
if (this.disposed) {
logger.warn('FileWatcher already disposed');
return;
}
logger.info('Disposing FileWatcher');
// 1. Stop watchers and clear timers (uses existing stop() logic)
this.stop();
// 2. Clear retry timer (stop() already handles this, but being explicit)
if (this.retryTimer) {
clearTimeout(this.retryTimer);
this.retryTimer = null;
}
// 3. Clear all debounce timers (stop() already handles this)
for (const timer of this.debounceTimers.values()) {
clearTimeout(timer);
}
this.debounceTimers.clear();
// 4. Clear catch-up timer (stop() already handles this)
if (this.catchUpTimer) {
clearInterval(this.catchUpTimer);
this.catchUpTimer = null;
}
// 5. Clear polling timer (stop() already handles this)
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
this.pollingTimer = null;
}
// 6. Clear all tracking maps (stop() already handles most of these)
this.lastProcessedLineCount.clear();
this.lastProcessedSize.clear();
this.activeSessionFiles.clear();
this.polledFileSizes.clear();
this.processingInProgress.clear();
this.pendingReprocess.clear();
// 7. Remove all EventEmitter listeners (MUST be last)
this.removeAllListeners();
// 8. Mark as disposed
this.disposed = true;
logger.info('FileWatcher disposed');
}
/**
* Starts the projects directory watcher.
*/