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
JavaScript
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();
}