Fix connection leak in route-connection-handler by using safe socket creation

The previous fix only addressed ForwardingHandler classes but missed the critical setupDirectConnection() method in route-connection-handler.ts where SmartProxy actually handles connections. This caused active connections to rise indefinitely on ECONNREFUSED errors.

Changes:
- Import createSocketWithErrorHandler in route-connection-handler.ts
- Replace net.connect() with createSocketWithErrorHandler() in setupDirectConnection()
- Properly clean up connection records when server connection fails
- Add connectionFailed flag to prevent setup of failed connections

This ensures connection records are cleaned up immediately when backend connections fail, preventing memory leaks.
This commit is contained in:
Philipp Kunz 2025-06-01 13:42:46 +00:00
parent 900942a263
commit 300ab1a077
2 changed files with 55 additions and 8 deletions

View File

@ -457,4 +457,11 @@ const socket = createSocketWithErrorHandler({
- `test/test.forwarding-error-fix.node.ts` - Tests forwarding handlers handle errors gracefully
### Configuration
No configuration changes needed. The fix is transparent to users.
No configuration changes needed. The fix is transparent to users.
### Important Note
The fix was applied in two places:
1. **ForwardingHandler classes** (`https-passthrough-handler.ts`, etc.) - These are standalone forwarding utilities
2. **SmartProxy route-connection-handler** (`route-connection-handler.ts`) - This is where the actual SmartProxy connection handling happens
The critical fix for SmartProxy was in `setupDirectConnection()` method in route-connection-handler.ts, which now uses `createSocketWithErrorHandler()` to properly handle connection failures and clean up connection records.

View File

@ -9,7 +9,7 @@ import { TlsManager } from './tls-manager.js';
import { HttpProxyBridge } from './http-proxy-bridge.js';
import { TimeoutManager } from './timeout-manager.js';
import { RouteManager } from './route-manager.js';
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers } from '../../core/utils/socket-utils.js';
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
/**
* Handles new connection processing and setup logic with support for route-based configuration
@ -1073,13 +1073,52 @@ export class RouteConnectionHandler {
record.pendingDataSize = initialChunk.length;
}
// Create the target socket
const targetSocket = plugins.net.connect(connectionOptions);
record.outgoing = targetSocket;
record.outgoingStartTime = Date.now();
// Create the target socket with immediate error handling
let targetSocket: plugins.net.Socket;
// Flag to track if initial connection failed
let connectionFailed = false;
targetSocket = createSocketWithErrorHandler({
port: finalTargetPort,
host: finalTargetHost,
onError: (error) => {
// Mark connection as failed
connectionFailed = true;
// Connection failed - clean up immediately
logger.log('error',
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${(error as any).code})`,
{
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
errorMessage: error.message,
errorCode: (error as any).code,
component: 'route-handler'
}
);
// Resume the incoming socket to prevent it from hanging
socket.resume();
// Clean up the incoming socket
if (!socket.destroyed) {
socket.destroy();
}
// Clean up the connection record
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${(error as any).code || 'unknown'}`);
}
});
// Only proceed with setup if connection didn't fail immediately
if (!connectionFailed) {
record.outgoing = targetSocket;
record.outgoingStartTime = Date.now();
// Apply socket optimizations
targetSocket.setNoDelay(this.settings.noDelay);
// Apply socket optimizations
targetSocket.setNoDelay(this.settings.noDelay);
// Apply keep-alive settings if enabled
if (this.settings.keepAlive) {
@ -1346,5 +1385,6 @@ export class RouteConnectionHandler {
record.tlsHandshakeComplete = true;
}
});
} // End of if (!connectionFailed)
}
}