fix(runtime): prevent memory leaks and improve shutdown/stream handling across services
This commit is contained in:
10
changelog.md
10
changelog.md
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-19 - 6.13.2 - fix(runtime)
|
||||
prevent memory leaks and improve shutdown/stream handling across services
|
||||
|
||||
- Add CertProvisionScheduler.clear() to reset in-memory backoff cache and call it during DcRouter shutdown
|
||||
- Stop any existing SmartAcme instance before creating a new one (await stop and log errors) to avoid duplicate running instances
|
||||
- Null out many DcRouter service references and clear certificateStatusMap on shutdown to allow GC of stopped services
|
||||
- Cap emailMetrics.recipients map size and trim to ~80% of MAX_TOP_DOMAINS to prevent unbounded growth
|
||||
- Await virtualStream.sendData in logs follow handler and clear the interval if the stream errors/closes to avoid interval leaks
|
||||
- Limit normalizedMacCache size and evict oldest entries when it exceeds 10000 to prevent unbounded cache growth
|
||||
|
||||
## 2026-02-18 - 6.13.1 - fix(dcrouter)
|
||||
enable PROXY protocol v1 handling for SmartProxy when remoteIngress is enabled to preserve client IPs
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.13.1',
|
||||
version: '6.13.2',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -106,6 +106,13 @@ export class CertProvisionScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all in-memory backoff cache entries
|
||||
*/
|
||||
public clear(): void {
|
||||
this.backoffCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backoff info for UI display
|
||||
*/
|
||||
|
||||
@@ -534,6 +534,12 @@ export class DcRouter {
|
||||
|
||||
// If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
|
||||
if (challengeHandlers.length > 0) {
|
||||
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
|
||||
if (this.smartAcme) {
|
||||
await this.smartAcme.stop().catch(err =>
|
||||
console.error('[DcRouter] Error stopping old SmartAcme:', err)
|
||||
);
|
||||
}
|
||||
this.smartAcme = new plugins.smartacme.SmartAcme({
|
||||
accountEmail: acmeConfig?.accountEmail || this.options.tls?.contactEmail || 'admin@example.com',
|
||||
certManager: new StorageBackedCertManager(this.storageManager),
|
||||
@@ -944,6 +950,25 @@ export class DcRouter {
|
||||
await this.cacheDb.stop().catch(err => console.error('Error stopping CacheDb:', err));
|
||||
}
|
||||
|
||||
// Clear backoff cache in cert scheduler
|
||||
if (this.certProvisionScheduler) {
|
||||
this.certProvisionScheduler.clear();
|
||||
}
|
||||
|
||||
// Allow GC of stopped services by nulling references
|
||||
this.smartProxy = undefined;
|
||||
this.emailServer = undefined;
|
||||
this.dnsServer = undefined;
|
||||
this.metricsManager = undefined;
|
||||
this.cacheCleaner = undefined;
|
||||
this.cacheDb = undefined;
|
||||
this.tunnelManager = undefined;
|
||||
this.radiusServer = undefined;
|
||||
this.smartAcme = undefined;
|
||||
this.certProvisionScheduler = undefined;
|
||||
this.remoteIngressManager = undefined;
|
||||
this.certificateStatusMap.clear();
|
||||
|
||||
console.log('All DcRouter services stopped');
|
||||
} catch (error) {
|
||||
console.error('Error during DcRouter shutdown:', error);
|
||||
|
||||
@@ -279,6 +279,14 @@ export class MetricsManager {
|
||||
if (recipient) {
|
||||
const count = this.emailMetrics.recipients.get(recipient) || 0;
|
||||
this.emailMetrics.recipients.set(recipient, count + 1);
|
||||
|
||||
// Cap recipients map to prevent unbounded growth within a day
|
||||
if (this.emailMetrics.recipients.size > this.MAX_TOP_DOMAINS) {
|
||||
const sorted = Array.from(this.emailMetrics.recipients.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, Math.floor(this.MAX_TOP_DOMAINS * 0.8));
|
||||
this.emailMetrics.recipients = new Map(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveryTimeMs) {
|
||||
|
||||
@@ -148,17 +148,17 @@ export class LogsHandler {
|
||||
}
|
||||
|
||||
// For follow mode, simulate real-time log streaming
|
||||
intervalId = setInterval(() => {
|
||||
intervalId = setInterval(async () => {
|
||||
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
|
||||
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
|
||||
|
||||
|
||||
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
|
||||
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
|
||||
|
||||
|
||||
// Filter by requested criteria
|
||||
if (levelFilter && !levelFilter.includes(mockLevel)) return;
|
||||
if (categoryFilter && !categoryFilter.includes(mockCategory)) return;
|
||||
|
||||
|
||||
const logEntry = {
|
||||
timestamp: Date.now(),
|
||||
level: mockLevel,
|
||||
@@ -168,10 +168,16 @@ export class LogsHandler {
|
||||
requestId: plugins.uuid.v4(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const logData = JSON.stringify(logEntry);
|
||||
const encoder = new TextEncoder();
|
||||
virtualStream.sendData(encoder.encode(logData));
|
||||
try {
|
||||
await virtualStream.sendData(encoder.encode(logData));
|
||||
} catch {
|
||||
// Stream closed or errored — clean up to prevent interval leak
|
||||
clearInterval(intervalId!);
|
||||
intervalId = null;
|
||||
}
|
||||
}, 2000); // Send a log every 2 seconds
|
||||
|
||||
// TODO: Hook into actual logger events
|
||||
|
||||
@@ -100,6 +100,14 @@ export class VlanManager {
|
||||
// Cache the result
|
||||
this.normalizedMacCache.set(mac, normalized);
|
||||
|
||||
// Prevent unbounded cache growth
|
||||
if (this.normalizedMacCache.size > 10000) {
|
||||
const iterator = this.normalizedMacCache.keys();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
this.normalizedMacCache.delete(iterator.next().value);
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.13.1',
|
||||
version: '6.13.2',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user