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:
parent
777d93f968
commit
767c985947
2 changed files with 95 additions and 0 deletions
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue