This commit is contained in:
2025-05-23 01:00:37 +00:00
parent 4905595cbb
commit 7344bf0f70
5 changed files with 119 additions and 33 deletions

View File

@ -53,6 +53,11 @@ export class ConnectionManager implements IConnectionManager {
*/
private resourceCheckInterval: NodeJS.Timeout | null = null;
/**
* Track cleanup timers so we can clear them
*/
private cleanupTimers: Set<NodeJS.Timeout> = new Set();
/**
* SMTP server options with enhanced resource controls
*/
@ -531,7 +536,7 @@ export class ConnectionManager implements IConnectionManager {
let buffer = '';
let totalBytesReceived = 0;
socket.on('data', (data) => {
socket.on('data', async (data) => {
try {
// Get current session and update activity timestamp
const session = this.smtpServer.getSessionManager().getSession(socket);
@ -546,7 +551,8 @@ export class ConnectionManager implements IConnectionManager {
try {
const dataString = data.toString('utf8');
// Use a special prefix to indicate this is raw data, not a command line
this.smtpServer.getCommandHandler().processCommand(socket, `__RAW_DATA__${dataString}`);
// CRITICAL FIX: Must await to prevent async pile-up
await this.smtpServer.getCommandHandler().processCommand(socket, `__RAW_DATA__${dataString}`);
return;
} catch (dataError) {
SmtpLogger.error(`Data handler error during DATA mode: ${dataError instanceof Error ? dataError.message : String(dataError)}`);
@ -599,15 +605,17 @@ export class ConnectionManager implements IConnectionManager {
// Process non-empty lines
if (line.length > 0) {
try {
// In DATA state, the command handler will process the data differently
this.smtpServer.getCommandHandler().processCommand(socket, line);
} catch (cmdError) {
// CRITICAL FIX: Must await processCommand to prevent async pile-up
// This was causing the busy loop with high CPU usage when many empty lines were processed
await this.smtpServer.getCommandHandler().processCommand(socket, line);
} catch (error) {
// Handle any errors in command processing
SmtpLogger.error(`Command handler error: ${cmdError instanceof Error ? cmdError.message : String(cmdError)}`);
SmtpLogger.error(`Command handler error: ${error instanceof Error ? error.message : String(error)}`);
this.sendResponse(socket, `${SmtpResponseCode.LOCAL_ERROR} Internal server error`);
// If there's a severe error, close the connection
if (cmdError instanceof Error &&
(cmdError.message.includes('fatal') || cmdError.message.includes('critical'))) {
if (error instanceof Error &&
(error.message.includes('fatal') || error.message.includes('critical'))) {
socket.destroy();
return;
}
@ -685,10 +693,25 @@ export class ConnectionManager implements IConnectionManager {
// Send service closing notification
this.sendServiceClosing(socket);
// End the socket
// End the socket gracefully
socket.end();
// Force destroy after a short delay if not already destroyed
const destroyTimer = setTimeout(() => {
if (!socket.destroyed) {
socket.destroy();
}
this.cleanupTimers.delete(destroyTimer);
}, 100);
this.cleanupTimers.add(destroyTimer);
} catch (error) {
SmtpLogger.error(`Error closing connection: ${error instanceof Error ? error.message : String(error)}`);
// Force destroy on error
try {
socket.destroy();
} catch (e) {
// Ignore destroy errors
}
}
}
@ -858,12 +881,14 @@ export class ConnectionManager implements IConnectionManager {
socket.end();
// Set a forced close timeout in case socket.end() doesn't close the connection
setTimeout(() => {
const timeoutDestroyTimer = setTimeout(() => {
if (!socket.destroyed) {
SmtpLogger.warn(`Forcing destroy of timed out socket: ${socketId}`);
socket.destroy();
}
this.cleanupTimers.delete(timeoutDestroyTimer);
}, 5000); // 5 second grace period for socket to end properly
this.cleanupTimers.add(timeoutDestroyTimer);
} catch (error) {
SmtpLogger.error(`Error ending timed out socket: ${error instanceof Error ? error.message : String(error)}`);
@ -989,6 +1014,12 @@ export class ConnectionManager implements IConnectionManager {
this.resourceCheckInterval = null;
}
// Clear all cleanup timers
for (const timer of this.cleanupTimers) {
clearTimeout(timer);
}
this.cleanupTimers.clear();
// Close all active connections
this.closeAllConnections();