campverse-backend / Utils /memoryManager.js
CampVerse CI
Deploy backend from GitHub Actions
c904036
Raw History Blame Contribute Delete
11.4 kB
/**
* Memory Management Utilities
* Handles memory cleanup and prevents memory leaks
*/
const { logger } = require('../Middleware/errorHandler');
class MemoryManager {
constructor() {
this.cleanupIntervals = new Map();
this.memoryThreshold = 0.85; // 85% memory usage threshold
this.monitoringInterval = null;
this.globalObjects = new Map();
}
/**
* Initialize memory monitoring
*/
init() {
// Monitor memory usage every 30 seconds
this.monitoringInterval = setInterval(() => {
this.checkMemoryUsage();
}, 30000);
// Clean up global objects every 5 minutes
this.scheduleCleanup('globalObjects', () => {
this.cleanupGlobalObjects();
}, 5 * 60 * 1000);
// Clean up expired QR scan results every 10 minutes
this.scheduleCleanup('qrScanResults', () => {
this.cleanupQrScanResults();
}, 10 * 60 * 1000);
// Clean up rate limit data every 15 minutes
this.scheduleCleanup('rateLimitData', () => {
this.cleanupRateLimitData();
}, 15 * 60 * 1000);
logger.info('Memory manager initialized');
}
/**
* Check current memory usage
*/
checkMemoryUsage() {
const memUsage = process.memoryUsage();
const totalMemory = require('os').totalmem();
const usedMemory = memUsage.rss;
const memoryPercentage = usedMemory / totalMemory;
// Log memory stats
logger.debug('Memory Usage:', {
rss: `${Math.round(memUsage.rss / 1024 / 1024)}MB`,
heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)}MB`,
heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`,
external: `${Math.round(memUsage.external / 1024 / 1024)}MB`,
percentage: `${Math.round(memoryPercentage * 100)}%`
});
// Trigger cleanup if memory usage is high
if (memoryPercentage > this.memoryThreshold) {
logger.warn(`High memory usage detected: ${Math.round(memoryPercentage * 100)}%`);
this.forceCleanup();
}
}
/**
* Schedule periodic cleanup
*/
scheduleCleanup(name, cleanupFunction, interval) {
if (this.cleanupIntervals.has(name)) {
clearInterval(this.cleanupIntervals.get(name));
}
const intervalId = setInterval(() => {
try {
cleanupFunction();
logger.debug(`Cleanup completed: ${name}`);
} catch (error) {
logger.error(`Cleanup error for ${name}:`, error);
}
}, interval);
this.cleanupIntervals.set(name, intervalId);
}
/**
* Clean up global objects that may cause memory leaks
*/
cleanupGlobalObjects() {
try {
// Clean up QR scan rate limiting
if (global.scanRateLimit) {
const now = Date.now();
const expiredKeys = [];
for (const [key, timestamp] of Object.entries(global.scanRateLimit)) {
if (now - timestamp > 60000) { // 1 minute expiry
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => {
try {
delete global.scanRateLimit[key];
} catch (err) {
logger.error(`Failed to delete scan rate limit key ${key}:`, err);
}
});
if (expiredKeys.length > 0) {
logger.debug(`Cleaned up ${expiredKeys.length} expired QR scan rate limit entries`);
}
}
// Clean up any other global objects
this.globalObjects.forEach((value, key) => {
try {
if (value.expiry && Date.now() > value.expiry) {
this.globalObjects.delete(key);
logger.debug(`Cleaned up expired global object: ${key}`);
}
} catch (err) {
logger.error(`Error cleaning up global object ${key}:`, err);
// Remove problematic objects
this.globalObjects.delete(key);
}
});
// Clean up any other potential global objects
const globalKeys = Object.keys(global);
const suspiciousKeys = globalKeys.filter(key =>
key.includes('temp') ||
key.includes('cache') ||
key.includes('rateLimit') ||
key.includes('session')
);
suspiciousKeys.forEach(key => {
try {
if (global[key] && typeof global[key] === 'object') {
const obj = global[key];
if (obj.timestamp && Date.now() - obj.timestamp > 300000) { // 5 minutes
delete global[key];
logger.debug(`Cleaned up suspicious global object: ${key}`);
}
}
} catch (err) {
logger.error(`Error cleaning up suspicious global object ${key}:`, err);
}
});
} catch (error) {
logger.error('Error in global objects cleanup:', error);
}
}
/**
* Clean up QR scan results
*/
cleanupQrScanResults() {
// This would clean up any QR scan result caches
// Implementation depends on how QR results are stored
if (global.qrScanResults) {
const now = Date.now();
const expiredKeys = [];
for (const [key, data] of Object.entries(global.qrScanResults)) {
if (data.timestamp && now - data.timestamp > 24 * 60 * 60 * 1000) { // 24 hours
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => delete global.qrScanResults[key]);
if (expiredKeys.length > 0) {
logger.debug(`Cleaned up ${expiredKeys.length} expired QR scan results`);
}
}
}
/**
* Clean up rate limit data
*/
cleanupRateLimitData() {
// Clean up any in-memory rate limit data
if (global.rateLimitData) {
const now = Date.now();
const expiredKeys = [];
for (const [key, data] of Object.entries(global.rateLimitData)) {
if (data.resetTime && now > data.resetTime) {
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => delete global.rateLimitData[key]);
if (expiredKeys.length > 0) {
logger.debug(`Cleaned up ${expiredKeys.length} expired rate limit entries`);
}
}
}
/**
* Force garbage collection and cleanup
*/
forceCleanup() {
logger.info('Forcing memory cleanup due to high usage');
// Run all cleanup functions immediately
this.cleanupGlobalObjects();
this.cleanupQrScanResults();
this.cleanupRateLimitData();
// Force garbage collection if available
if (global.gc) {
global.gc();
logger.info('Forced garbage collection completed');
} else {
logger.warn('Garbage collection not available. Start Node.js with --expose-gc flag');
}
}
/**
* Set a global object with automatic cleanup
*/
setGlobalObject(key, value, ttl = 60000) {
this.globalObjects.set(key, {
value,
expiry: Date.now() + ttl,
createdAt: Date.now()
});
}
/**
* Get a global object
*/
getGlobalObject(key) {
const obj = this.globalObjects.get(key);
if (!obj) return null;
if (Date.now() > obj.expiry) {
this.globalObjects.delete(key);
return null;
}
return obj.value;
}
/**
* Get memory statistics
*/
getMemoryStats() {
const memUsage = process.memoryUsage();
const totalMemory = require('os').totalmem();
const freeMemory = require('os').freemem();
return {
process: {
rss: memUsage.rss,
heapTotal: memUsage.heapTotal,
heapUsed: memUsage.heapUsed,
external: memUsage.external,
arrayBuffers: memUsage.arrayBuffers
},
system: {
total: totalMemory,
free: freeMemory,
used: totalMemory - freeMemory,
percentage: ((totalMemory - freeMemory) / totalMemory) * 100
},
globalObjects: this.globalObjects.size,
cleanupIntervals: this.cleanupIntervals.size,
timestamp: new Date().toISOString()
};
}
/**
* Create a memory-safe timeout
*/
createSafeTimeout(callback, delay, ...args) {
const timeoutId = setTimeout(() => {
try {
callback(...args);
} catch (error) {
logger.error('Safe timeout callback error:', error);
}
}, delay);
return timeoutId;
}
/**
* Create a memory-safe interval
*/
createSafeInterval(callback, interval, ...args) {
const intervalId = setInterval(() => {
try {
callback(...args);
} catch (error) {
logger.error('Safe interval callback error:', error);
}
}, interval);
return intervalId;
}
/**
* Cleanup and shutdown
*/
shutdown() {
logger.info('Shutting down memory manager');
// Clear monitoring interval
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
}
// Clear all cleanup intervals
this.cleanupIntervals.forEach((intervalId, name) => {
clearInterval(intervalId);
logger.debug(`Cleared cleanup interval: ${name}`);
});
this.cleanupIntervals.clear();
// Final cleanup
this.forceCleanup();
// Clear global objects
this.globalObjects.clear();
logger.info('Memory manager shutdown completed');
}
}
// Utility functions for memory management
const memoryUtils = {
/**
* Convert bytes to human readable format
*/
formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
},
/**
* Deep clone object safely
*/
safeClone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
logger.error('Safe clone error:', error);
return null;
}
},
/**
* Check if object is empty
*/
isEmpty(obj) {
if (obj == null) return true;
if (Array.isArray(obj) || typeof obj === 'string') return obj.length === 0;
return Object.keys(obj).length === 0;
},
/**
* Safely delete object properties
*/
safeDelete(obj, ...keys) {
if (!obj || typeof obj !== 'object') return;
keys.forEach(key => {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
delete obj[key];
}
});
},
/**
* Create a weak reference map for caching
*/
createWeakCache() {
return new WeakMap();
},
/**
* Throttle function execution to prevent memory buildup
*/
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
/**
* Debounce function execution
*/
debounce(func, wait, immediate) {
let timeout;
return function() {
const context = this;
const args = arguments;
const later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
};
// Create singleton instance
const memoryManager = new MemoryManager();
module.exports = {
memoryManager,
memoryUtils
};