UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

273 lines (272 loc) 10 kB
import logger from '../logger.js'; import { setTimeout as setTimeoutOriginal, clearTimeout, setInterval as setIntervalOriginal, clearInterval } from 'timers'; export class ResourceCleanupManager { static trackedTimers = new Set(); static trackedListeners = new Set(); static trackedPromises = new Set(); static isTestEnvironment = false; static isInitialized = false; static originalTimerFunctions = null; static initialize() { if (process.env.NODE_ENV !== 'test' && !process.env.VITEST) { logger.warn('ResourceCleanupManager should only be used in test environment'); return; } this.isTestEnvironment = true; this.setupTimerTracking(); this.setupProcessHandlers(); this.isInitialized = true; logger.debug('ResourceCleanupManager initialized for test environment'); } static setupTimerTracking() { if (!this.isTestEnvironment || this.originalTimerFunctions) { return; } this.originalTimerFunctions = { setTimeout: global.setTimeout, setInterval: global.setInterval, clearTimeout: global.clearTimeout, clearInterval: global.clearInterval }; global.setTimeout = ((callback, ms, ...args) => { const timer = setTimeoutOriginal(callback, ms, ...args); this.trackedTimers.add({ id: timer, type: 'timeout', createdAt: Date.now(), stack: new Error().stack }); return timer; }); global.setInterval = ((callback, ms, ...args) => { const timer = setIntervalOriginal(callback, ms, ...args); this.trackedTimers.add({ id: timer, type: 'interval', createdAt: Date.now(), stack: new Error().stack }); return timer; }); global.clearTimeout = ((timer) => { this.originalTimerFunctions.clearTimeout(timer); for (const tracked of this.trackedTimers) { if (tracked.id === timer) { this.trackedTimers.delete(tracked); break; } } }); global.clearInterval = ((timer) => { this.originalTimerFunctions.clearInterval(timer); for (const tracked of this.trackedTimers) { if (tracked.id === timer) { this.trackedTimers.delete(tracked); break; } } }); logger.debug('Timer tracking setup completed'); } static setupProcessHandlers() { if (!this.isTestEnvironment) { return; } process.on('uncaughtException', (error) => { logger.error({ err: error }, 'Uncaught exception detected during test'); this.logResourceStats(); }); process.on('unhandledRejection', (reason, promise) => { logger.error({ reason, promise }, 'Unhandled promise rejection detected'); this.logResourceStats(); }); logger.debug('Process handlers setup completed'); } static trackEventListener(target, event, listener) { if (!this.isTestEnvironment) { return; } this.trackedListeners.add({ target, event, listener, createdAt: Date.now() }); logger.debug({ event, listenerCount: this.trackedListeners.size }, 'Event listener tracked'); } static trackPromise(promise) { if (!this.isTestEnvironment) { return; } this.trackedPromises.add({ promise, createdAt: Date.now(), stack: new Error().stack }); promise.finally(() => { for (const tracked of this.trackedPromises) { if (tracked.promise === promise) { this.trackedPromises.delete(tracked); break; } } }); logger.debug({ promiseCount: this.trackedPromises.size }, 'Promise tracked'); } static async cleanupResources() { if (!this.isTestEnvironment || !this.isInitialized) { return; } const startTime = Date.now(); const cleaned = { timers: 0, listeners: 0, promises: 0 }; try { for (const timer of this.trackedTimers) { try { if (timer.type === 'timeout') { clearTimeout(timer.id); } else { clearInterval(timer.id); } cleaned.timers++; } catch (error) { logger.warn({ err: error, timer: timer.id }, 'Failed to clear timer'); } } this.trackedTimers.clear(); for (const listener of this.trackedListeners) { try { if ('removeEventListener' in listener.target) { listener.target.removeEventListener(listener.event, listener.listener); } else if ('removeListener' in listener.target) { listener.target.removeListener(listener.event, listener.listener); } cleaned.listeners++; } catch (error) { logger.warn({ err: error, event: listener.event }, 'Failed to remove event listener'); } } this.trackedListeners.clear(); if (this.trackedPromises.size > 0) { const promises = Array.from(this.trackedPromises).map(tracked => tracked.promise); try { await Promise.allSettled(promises.map(p => Promise.race([p, new Promise(resolve => setTimeout(resolve, 1000))]))); cleaned.promises = promises.length; } catch (error) { logger.warn({ err: error }, 'Failed to wait for promises to settle'); } } this.trackedPromises.clear(); if (global.gc) { global.gc(); } const duration = Date.now() - startTime; logger.info({ cleaned, duration, memoryAfter: process.memoryUsage() }, 'Resource cleanup completed'); } catch (error) { logger.error({ err: error }, 'Resource cleanup failed'); throw error; } } static getResourceStats() { const memoryUsage = process.memoryUsage(); const handles = process._getActiveHandles?.()?.length || 0; return { timers: this.trackedTimers.size, listeners: this.trackedListeners.size, promises: this.trackedPromises.size, memoryUsage, handles }; } static logResourceStats() { if (!this.isTestEnvironment) { return; } const stats = this.getResourceStats(); logger.info(stats, 'Current resource statistics'); } static checkForLeaks() { if (!this.isTestEnvironment) { return { hasLeaks: false, leaks: [] }; } const leaks = []; const now = Date.now(); const maxAge = 30000; for (const timer of this.trackedTimers) { if (now - timer.createdAt > maxAge) { leaks.push(`Long-running ${timer.type}: ${timer.id} (age: ${now - timer.createdAt}ms)`); } } for (const listener of this.trackedListeners) { if (now - listener.createdAt > maxAge) { leaks.push(`Long-running listener: ${listener.event} (age: ${now - listener.createdAt}ms)`); } } for (const promise of this.trackedPromises) { if (now - promise.createdAt > maxAge) { leaks.push(`Long-running promise (age: ${now - promise.createdAt}ms)`); } } return { hasLeaks: leaks.length > 0, leaks }; } static restoreOriginalFunctions() { if (!this.isTestEnvironment || !this.originalTimerFunctions) { return; } global.setTimeout = this.originalTimerFunctions.setTimeout; global.setInterval = this.originalTimerFunctions.setInterval; global.clearTimeout = this.originalTimerFunctions.clearTimeout; global.clearInterval = this.originalTimerFunctions.clearInterval; this.originalTimerFunctions = null; logger.debug('Original timer functions restored'); } static reset() { if (!this.isTestEnvironment) { return; } this.trackedTimers.clear(); this.trackedListeners.clear(); this.trackedPromises.clear(); this.restoreOriginalFunctions(); this.isInitialized = false; logger.debug('ResourceCleanupManager reset'); } static isManagerInitialized() { return this.isInitialized && this.isTestEnvironment; } } export function initializeResourceCleanupManager() { ResourceCleanupManager.initialize(); } export async function cleanupResources() { await ResourceCleanupManager.cleanupResources(); } export function trackPromise(promise) { ResourceCleanupManager.trackPromise(promise); return promise; } export function trackEventListener(target, event, listener) { ResourceCleanupManager.trackEventListener(target, event, listener); } export function getResourceStats() { return ResourceCleanupManager.getResourceStats(); } export function checkForResourceLeaks() { return ResourceCleanupManager.checkForLeaks(); }