From 02dd3c77b5511f7ebe8779db026fd12fca9141d0 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Mon, 9 Jun 2025 17:18:50 +0000 Subject: [PATCH] fix: update @push.rocks/smartproxy to version 19.6.1 and improve socket management in ConnectionManager feat: enhance MetricsManager with reset interval and top domains tracking --- package.json | 2 +- pnpm-lock.yaml | 14 ++++---- ts/mail/delivery/classes.delivery.system.ts | 3 +- .../delivery/smtpserver/connection-manager.ts | 32 ++++++++++++++----- ts/monitoring/classes.metricsmanager.ts | 28 ++++++++++++++-- 5 files changed, 60 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 695b899..e002c49 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@push.rocks/smartnetwork": "^4.0.2", "@push.rocks/smartpath": "^5.0.5", "@push.rocks/smartpromise": "^4.0.3", - "@push.rocks/smartproxy": "^19.6.0", + "@push.rocks/smartproxy": "^19.6.1", "@push.rocks/smartrequest": "^2.1.0", "@push.rocks/smartrule": "^2.0.1", "@push.rocks/smartrx": "^3.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b9b357..a744696 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,8 +72,8 @@ importers: specifier: ^4.0.3 version: 4.2.3 '@push.rocks/smartproxy': - specifier: ^19.6.0 - version: 19.6.0(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4) + specifier: ^19.6.1 + version: 19.6.1(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4) '@push.rocks/smartrequest': specifier: ^2.1.0 version: 2.1.0 @@ -1062,8 +1062,8 @@ packages: '@push.rocks/smartpromise@4.2.3': resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==} - '@push.rocks/smartproxy@19.6.0': - resolution: {integrity: sha512-qMayuTkKpgQM14Z2cksjG8NmF6KLogcmXQIKUSFx/W7kKoGSkZ+/kYbnngDpc87n86nathr0p2H2euUt0lHfRQ==} + '@push.rocks/smartproxy@19.6.1': + resolution: {integrity: sha512-CkztOqRR1i0icOPad5TlzjrNAXvr/L3BMjlo+fhp3c/dPuXV9Z/isOECz0Flk0A3anKIgCqC9pGNzdNdssLUsQ==} '@push.rocks/smartpuppeteer@2.0.5': resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==} @@ -5440,10 +5440,8 @@ snapshots: '@push.rocks/taskbuffer': 3.1.7 transitivePeerDependencies: - '@nuxt/kit' - - bufferutil - react - supports-color - - utf-8-validate - vue '@hapi/hoek@9.3.0': {} @@ -5786,6 +5784,7 @@ snapshots: - '@mongodb-js/zstd' - '@nuxt/kit' - aws-crt + - bufferutil - encoding - gcp-metadata - kerberos @@ -5794,6 +5793,7 @@ snapshots: - snappy - socks - supports-color + - utf-8-validate - vue '@push.rocks/smartarchive@3.0.8': @@ -6223,7 +6223,7 @@ snapshots: '@push.rocks/smartpromise@4.2.3': {} - '@push.rocks/smartproxy@19.6.0(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)': + '@push.rocks/smartproxy@19.6.1(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)': dependencies: '@push.rocks/lik': 6.2.2 '@push.rocks/smartacme': 8.0.0(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4) diff --git a/ts/mail/delivery/classes.delivery.system.ts b/ts/mail/delivery/classes.delivery.system.ts index 977018b..7df7a40 100644 --- a/ts/mail/delivery/classes.delivery.system.ts +++ b/ts/mail/delivery/classes.delivery.system.ts @@ -221,12 +221,13 @@ export class MultiModeDeliverySystem extends EventEmitter { const checkInterval = setInterval(() => { if (this.activeDeliveries.size === 0) { clearInterval(checkInterval); + clearTimeout(forceTimeout); resolve(); } }, 1000); // Force resolve after 30 seconds - setTimeout(() => { + const forceTimeout = setTimeout(() => { clearInterval(checkInterval); resolve(); }, 30000); diff --git a/ts/mail/delivery/smtpserver/connection-manager.ts b/ts/mail/delivery/smtpserver/connection-manager.ts index b01d456..02b27c9 100644 --- a/ts/mail/delivery/smtpserver/connection-manager.ts +++ b/ts/mail/delivery/smtpserver/connection-manager.ts @@ -247,11 +247,23 @@ export class ConnectionManager implements IConnectionManager { // 2. Check for destroyed sockets in active connections let destroyedSocketsCount = 0; + const socketsToRemove: Array = []; + for (const socket of this.activeConnections) { if (socket.destroyed) { destroyedSocketsCount++; - // This should not happen - remove destroyed sockets from tracking - this.activeConnections.delete(socket); + socketsToRemove.push(socket); + } + } + + // Remove destroyed sockets from tracking + for (const socket of socketsToRemove) { + this.activeConnections.delete(socket); + // Also ensure all listeners are removed + try { + socket.removeAllListeners(); + } catch { + // Ignore errors from removeAllListeners } } @@ -341,9 +353,6 @@ export class ConnectionManager implements IConnectionManager { SmtpLogger.debug(`Could not set socket buffer limits: ${error instanceof Error ? error.message : String(error)}`); } - // Track this IP connection - this.trackIPConnection(remoteAddress); - // Set up event handlers this.setupSocketEventHandlers(socket); @@ -498,9 +507,6 @@ export class ConnectionManager implements IConnectionManager { SmtpLogger.debug(`Could not set socket buffer limits: ${error instanceof Error ? error.message : String(error)}`); } - // Track this IP connection - this.trackIPConnection(remoteAddress); - // Set up event handlers this.setupSocketEventHandlers(socket); @@ -763,6 +769,9 @@ export class ConnectionManager implements IConnectionManager { clearTimeout(session.dataTimeoutId); } + // Remove all event listeners to prevent memory leaks + socket.removeAllListeners(); + // Log connection close with session details if available adaptiveLogger.logConnection(socket, 'close', session); @@ -774,6 +783,13 @@ export class ConnectionManager implements IConnectionManager { // Ensure socket is removed from active connections even if an error occurs this.activeConnections.delete(socket); + + // Always try to remove all listeners even on error + try { + socket.removeAllListeners(); + } catch { + // Ignore errors from removeAllListeners + } } } diff --git a/ts/monitoring/classes.metricsmanager.ts b/ts/monitoring/classes.metricsmanager.ts index 3ec36d1..3bdc156 100644 --- a/ts/monitoring/classes.metricsmanager.ts +++ b/ts/monitoring/classes.metricsmanager.ts @@ -5,6 +5,10 @@ export class MetricsManager { private logger: plugins.smartlog.Smartlog; private smartMetrics: plugins.smartmetrics.SmartMetrics; private dcRouter: DcRouter; + private resetInterval?: NodeJS.Timeout; + + // Constants + private readonly MAX_TOP_DOMAINS = 1000; // Limit topDomains Map size // Track email-specific metrics private emailMetrics = { @@ -54,7 +58,7 @@ export class MetricsManager { this.smartMetrics.start(); // Reset daily counters at midnight - setInterval(() => { + this.resetInterval = setInterval(() => { const currentDate = new Date().toDateString(); if (currentDate !== this.emailMetrics.lastResetDate) { @@ -88,6 +92,12 @@ export class MetricsManager { } public async stop(): Promise { + // Clear the reset interval + if (this.resetInterval) { + clearInterval(this.resetInterval); + this.resetInterval = undefined; + } + this.smartMetrics.stop(); this.logger.log('info', 'MetricsManager stopped'); } @@ -232,9 +242,23 @@ export class MetricsManager { // Track query types this.dnsMetrics.queryTypes[queryType] = (this.dnsMetrics.queryTypes[queryType] || 0) + 1; - // Track top domains + // Track top domains with size limit const currentCount = this.dnsMetrics.topDomains.get(domain) || 0; this.dnsMetrics.topDomains.set(domain, currentCount + 1); + + // If we've exceeded the limit, remove the least accessed domains + if (this.dnsMetrics.topDomains.size > this.MAX_TOP_DOMAINS) { + // Convert to array, sort by count, and keep only top domains + const sortedDomains = Array.from(this.dnsMetrics.topDomains.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, Math.floor(this.MAX_TOP_DOMAINS * 0.8)); // Keep 80% to avoid frequent cleanup + + // Clear and repopulate with top domains + this.dnsMetrics.topDomains.clear(); + sortedDomains.forEach(([domain, count]) => { + this.dnsMetrics.topDomains.set(domain, count); + }); + } } // Security event tracking methods