# Connection Cleanup Code Patterns ## Pattern 1: Safe Connection Cleanup ```typescript public initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void { // Prevent duplicate cleanup if (record.incomingTerminationReason === null || record.incomingTerminationReason === undefined) { record.incomingTerminationReason = reason; this.incrementTerminationStat('incoming', reason); } this.cleanupConnection(record, reason); } public cleanupConnection(record: IConnectionRecord, reason: string = 'normal'): void { if (!record.connectionClosed) { record.connectionClosed = true; // Remove from tracking immediately this.connectionRecords.delete(record.id); this.securityManager.removeConnectionByIP(record.remoteIP, record.id); // Clear timers if (record.cleanupTimer) { clearTimeout(record.cleanupTimer); record.cleanupTimer = undefined; } // Clean up sockets this.cleanupSocket(record, 'incoming', record.incoming); if (record.outgoing) { this.cleanupSocket(record, 'outgoing', record.outgoing); } // Clear memory record.pendingData = []; record.pendingDataSize = 0; } } ``` ## Pattern 2: Socket Cleanup with Retry ```typescript private cleanupSocket( record: IConnectionRecord, side: 'incoming' | 'outgoing', socket: plugins.net.Socket ): void { try { if (!socket.destroyed) { // Graceful shutdown first socket.end(); // Force destroy after timeout const socketTimeout = setTimeout(() => { try { if (!socket.destroyed) { socket.destroy(); } } catch (err) { console.log(`[${record.id}] Error destroying ${side} socket: ${err}`); } }, 1000); // Don't block process exit if (socketTimeout.unref) { socketTimeout.unref(); } } } catch (err) { console.log(`[${record.id}] Error closing ${side} socket: ${err}`); // Fallback to destroy try { if (!socket.destroyed) { socket.destroy(); } } catch (destroyErr) { console.log(`[${record.id}] Error destroying ${side} socket: ${destroyErr}`); } } } ``` ## Pattern 3: NetworkProxy Bridge Cleanup ```typescript public async forwardToNetworkProxy( connectionId: string, socket: plugins.net.Socket, record: IConnectionRecord, initialChunk: Buffer, networkProxyPort: number, cleanupCallback: (reason: string) => void ): Promise { const proxySocket = new plugins.net.Socket(); // Connect to NetworkProxy await new Promise((resolve, reject) => { proxySocket.connect(networkProxyPort, 'localhost', () => { resolve(); }); proxySocket.on('error', reject); }); // Send initial data if (initialChunk) { proxySocket.write(initialChunk); } // Setup bidirectional piping socket.pipe(proxySocket); proxySocket.pipe(socket); // Comprehensive cleanup handler const cleanup = (reason: string) => { // Unpipe to prevent data loss socket.unpipe(proxySocket); proxySocket.unpipe(socket); // Destroy proxy socket proxySocket.destroy(); // Notify SmartProxy cleanupCallback(reason); }; // Setup all cleanup triggers socket.on('end', () => cleanup('socket_end')); socket.on('error', () => cleanup('socket_error')); proxySocket.on('end', () => cleanup('proxy_end')); proxySocket.on('error', () => cleanup('proxy_error')); } ``` ## Pattern 4: Error Handler with Cleanup ```typescript public handleError(side: 'incoming' | 'outgoing', record: IConnectionRecord) { return (err: Error) => { const code = (err as any).code; let reason = 'error'; // Map error codes to reasons switch (code) { case 'ECONNRESET': reason = 'econnreset'; break; case 'ETIMEDOUT': reason = 'etimedout'; break; case 'ECONNREFUSED': reason = 'connection_refused'; break; case 'EHOSTUNREACH': reason = 'host_unreachable'; break; } // Log with context const duration = Date.now() - record.incomingStartTime; console.log( `[${record.id}] ${code} on ${side} side from ${record.remoteIP}. ` + `Duration: ${plugins.prettyMs(duration)}` ); // Track termination reason if (side === 'incoming' && record.incomingTerminationReason === null) { record.incomingTerminationReason = reason; this.incrementTerminationStat('incoming', reason); } // Initiate cleanup this.initiateCleanupOnce(record, reason); }; } ``` ## Pattern 5: Inactivity Check with Cleanup ```typescript public performInactivityCheck(): void { const now = Date.now(); const connectionIds = [...this.connectionRecords.keys()]; for (const id of connectionIds) { const record = this.connectionRecords.get(id); if (!record) continue; // Skip if disabled or immortal if (this.settings.disableInactivityCheck || (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal')) { continue; } const inactivityTime = now - record.lastActivity; let effectiveTimeout = this.settings.inactivityTimeout!; // Extended timeout for keep-alive if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') { effectiveTimeout *= (this.settings.keepAliveInactivityMultiplier || 6); } if (inactivityTime > effectiveTimeout && !record.connectionClosed) { // Warn before closing keep-alive connections if (record.hasKeepAlive && !record.inactivityWarningIssued) { console.log(`[${id}] Warning: Keep-alive connection inactive`); record.inactivityWarningIssued = true; // Grace period record.lastActivity = now - (effectiveTimeout - 600000); } else { // Close the connection console.log(`[${id}] Closing due to inactivity`); this.cleanupConnection(record, 'inactivity'); } } } } ``` ## Pattern 6: Complete Shutdown ```typescript public clearConnections(): void { const connectionIds = [...this.connectionRecords.keys()]; // Phase 1: Graceful end for (const id of connectionIds) { const record = this.connectionRecords.get(id); if (record) { try { // Clear timers if (record.cleanupTimer) { clearTimeout(record.cleanupTimer); record.cleanupTimer = undefined; } // Graceful socket end if (record.incoming && !record.incoming.destroyed) { record.incoming.end(); } if (record.outgoing && !record.outgoing.destroyed) { record.outgoing.end(); } } catch (err) { console.log(`Error during graceful end: ${err}`); } } } // Phase 2: Force destroy after delay setTimeout(() => { for (const id of connectionIds) { const record = this.connectionRecords.get(id); if (record) { try { // Remove all listeners if (record.incoming) { record.incoming.removeAllListeners(); if (!record.incoming.destroyed) { record.incoming.destroy(); } } if (record.outgoing) { record.outgoing.removeAllListeners(); if (!record.outgoing.destroyed) { record.outgoing.destroy(); } } } catch (err) { console.log(`Error during forced destruction: ${err}`); } } } // Clear all tracking this.connectionRecords.clear(); this.terminationStats = { incoming: {}, outgoing: {} }; }, 100); } ``` ## Pattern 7: Safe Event Handler Removal ```typescript // Store handlers for later removal record.renegotiationHandler = this.tlsManager.createRenegotiationHandler( connectionId, serverName, connInfo, (connectionId, reason) => this.connectionManager.initiateCleanupOnce(record, reason) ); // Add the handler socket.on('data', record.renegotiationHandler); // Remove during cleanup if (record.incoming) { try { record.incoming.removeAllListeners('data'); record.renegotiationHandler = undefined; } catch (err) { console.log(`[${record.id}] Error removing data handlers: ${err}`); } } ``` ## Pattern 8: Connection State Tracking ```typescript interface IConnectionRecord { id: string; connectionClosed: boolean; incomingTerminationReason: string | null; outgoingTerminationReason: string | null; cleanupTimer?: NodeJS.Timeout; renegotiationHandler?: Function; // ... other fields } // Check state before operations if (!record.connectionClosed) { // Safe to perform operations } // Track cleanup state record.connectionClosed = true; ``` ## Key Principles 1. **Idempotency**: Cleanup operations should be safe to call multiple times 2. **State Tracking**: Always track connection and cleanup state 3. **Error Resilience**: Handle errors during cleanup gracefully 4. **Resource Release**: Clear all references (timers, handlers, buffers) 5. **Graceful First**: Try graceful shutdown before forced destroy 6. **Comprehensive Coverage**: Handle all possible termination scenarios 7. **Logging**: Track termination reasons for debugging 8. **Memory Safety**: Clear data structures to prevent leaks