- Renamed port proxy and SNI handler source files to classes.pp.portproxy.js and classes.pp.snihandler.js respectively - Updated import paths in index.ts and test files (e.g. in test.ts and test.router.ts) to reference the new file names - This refactor improves code organization but breaks direct imports from the old paths
190 lines
5.8 KiB
TypeScript
190 lines
5.8 KiB
TypeScript
import type { IConnectionRecord, IPortProxySettings } from './classes.pp.interfaces.js';
|
|
|
|
/**
|
|
* Manages timeouts and inactivity tracking for connections
|
|
*/
|
|
export class TimeoutManager {
|
|
constructor(private settings: IPortProxySettings) {}
|
|
|
|
/**
|
|
* Ensure timeout values don't exceed Node.js max safe integer
|
|
*/
|
|
public ensureSafeTimeout(timeout: number): number {
|
|
const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
|
|
return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* Generate a slightly randomized timeout to prevent thundering herd
|
|
*/
|
|
public randomizeTimeout(baseTimeout: number, variationPercent: number = 5): number {
|
|
const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
|
|
const variation = safeBaseTimeout * (variationPercent / 100);
|
|
return this.ensureSafeTimeout(
|
|
safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update connection activity timestamp
|
|
*/
|
|
public updateActivity(record: IConnectionRecord): void {
|
|
record.lastActivity = Date.now();
|
|
|
|
// Clear any inactivity warning
|
|
if (record.inactivityWarningIssued) {
|
|
record.inactivityWarningIssued = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate effective inactivity timeout based on connection type
|
|
*/
|
|
public getEffectiveInactivityTimeout(record: IConnectionRecord): number {
|
|
let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
|
|
|
|
// For immortal keep-alive connections, use an extremely long timeout
|
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
return Number.MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
// For extended keep-alive connections, apply multiplier
|
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
effectiveTimeout = effectiveTimeout * multiplier;
|
|
}
|
|
|
|
return this.ensureSafeTimeout(effectiveTimeout);
|
|
}
|
|
|
|
/**
|
|
* Calculate effective max lifetime based on connection type
|
|
*/
|
|
public getEffectiveMaxLifetime(record: IConnectionRecord): number {
|
|
// Use domain-specific timeout if available
|
|
const baseTimeout = record.domainConfig?.connectionTimeout ||
|
|
this.settings.maxConnectionLifetime ||
|
|
86400000; // 24 hours default
|
|
|
|
// For immortal keep-alive connections, use an extremely long lifetime
|
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
return Number.MAX_SAFE_INTEGER;
|
|
}
|
|
|
|
// For extended keep-alive connections, use the extended lifetime setting
|
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
return this.ensureSafeTimeout(
|
|
this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
|
|
);
|
|
}
|
|
|
|
// Apply randomization if enabled
|
|
if (this.settings.enableRandomizedTimeouts) {
|
|
return this.randomizeTimeout(baseTimeout);
|
|
}
|
|
|
|
return this.ensureSafeTimeout(baseTimeout);
|
|
}
|
|
|
|
/**
|
|
* Setup connection timeout
|
|
* @returns The cleanup timer
|
|
*/
|
|
public setupConnectionTimeout(
|
|
record: IConnectionRecord,
|
|
onTimeout: (record: IConnectionRecord, reason: string) => void
|
|
): NodeJS.Timeout {
|
|
// Clear any existing timer
|
|
if (record.cleanupTimer) {
|
|
clearTimeout(record.cleanupTimer);
|
|
}
|
|
|
|
// Calculate effective timeout
|
|
const effectiveLifetime = this.getEffectiveMaxLifetime(record);
|
|
|
|
// Set up the timeout
|
|
const timer = setTimeout(() => {
|
|
// Call the provided callback
|
|
onTimeout(record, 'connection_timeout');
|
|
}, effectiveLifetime);
|
|
|
|
// Make sure timeout doesn't keep the process alive
|
|
if (timer.unref) {
|
|
timer.unref();
|
|
}
|
|
|
|
return timer;
|
|
}
|
|
|
|
/**
|
|
* Check for inactivity on a connection
|
|
* @returns Object with check results
|
|
*/
|
|
public checkInactivity(record: IConnectionRecord): {
|
|
isInactive: boolean;
|
|
shouldWarn: boolean;
|
|
inactivityTime: number;
|
|
effectiveTimeout: number;
|
|
} {
|
|
// Skip for connections with inactivity check disabled
|
|
if (this.settings.disableInactivityCheck) {
|
|
return {
|
|
isInactive: false,
|
|
shouldWarn: false,
|
|
inactivityTime: 0,
|
|
effectiveTimeout: 0
|
|
};
|
|
}
|
|
|
|
// Skip for immortal keep-alive connections
|
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
return {
|
|
isInactive: false,
|
|
shouldWarn: false,
|
|
inactivityTime: 0,
|
|
effectiveTimeout: 0
|
|
};
|
|
}
|
|
|
|
const now = Date.now();
|
|
const inactivityTime = now - record.lastActivity;
|
|
const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
|
|
|
|
// Check if inactive
|
|
const isInactive = inactivityTime > effectiveTimeout;
|
|
|
|
// For keep-alive connections, we should warn first
|
|
const shouldWarn = record.hasKeepAlive &&
|
|
isInactive &&
|
|
!record.inactivityWarningIssued;
|
|
|
|
return {
|
|
isInactive,
|
|
shouldWarn,
|
|
inactivityTime,
|
|
effectiveTimeout
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Apply socket timeout settings
|
|
*/
|
|
public applySocketTimeouts(record: IConnectionRecord): void {
|
|
// Skip for immortal keep-alive connections
|
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
// Disable timeouts completely for immortal connections
|
|
record.incoming.setTimeout(0);
|
|
if (record.outgoing) {
|
|
record.outgoing.setTimeout(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Apply normal timeouts
|
|
const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
|
|
record.incoming.setTimeout(timeout);
|
|
if (record.outgoing) {
|
|
record.outgoing.setTimeout(timeout);
|
|
}
|
|
}
|
|
} |