fix(lifecycle): clean up service subscriptions, proxy retries, and stale runtime state on shutdown
This commit is contained in:
@@ -92,6 +92,8 @@ export interface IAccountingManagerConfig {
|
||||
detailedLogging?: boolean;
|
||||
/** Maximum active sessions to track in memory */
|
||||
maxActiveSessions?: number;
|
||||
/** Stale session timeout in hours — sessions with no update for this long are evicted (default: 24) */
|
||||
staleSessionTimeoutHours?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +107,7 @@ export class AccountingManager {
|
||||
private activeSessions: Map<string, IAccountingSession> = new Map();
|
||||
private config: Required<IAccountingManagerConfig>;
|
||||
private storageManager?: StorageManager;
|
||||
private staleSessionSweepTimer?: ReturnType<typeof setInterval>;
|
||||
|
||||
// Counters for statistics
|
||||
private stats = {
|
||||
@@ -121,6 +124,7 @@ export class AccountingManager {
|
||||
retentionDays: config?.retentionDays ?? 30,
|
||||
detailedLogging: config?.detailedLogging ?? false,
|
||||
maxActiveSessions: config?.maxActiveSessions ?? 10000,
|
||||
staleSessionTimeoutHours: config?.staleSessionTimeoutHours ?? 24,
|
||||
};
|
||||
this.storageManager = storageManager;
|
||||
}
|
||||
@@ -132,9 +136,60 @@ export class AccountingManager {
|
||||
if (this.storageManager) {
|
||||
await this.loadActiveSessions();
|
||||
}
|
||||
|
||||
// Start periodic sweep to evict stale sessions (every 15 minutes)
|
||||
this.staleSessionSweepTimer = setInterval(() => {
|
||||
this.sweepStaleSessions();
|
||||
}, 15 * 60 * 1000);
|
||||
// Allow the process to exit even if the timer is pending
|
||||
if (this.staleSessionSweepTimer.unref) {
|
||||
this.staleSessionSweepTimer.unref();
|
||||
}
|
||||
|
||||
logger.log('info', `AccountingManager initialized with ${this.activeSessions.size} active sessions`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the accounting manager and clean up timers
|
||||
*/
|
||||
stop(): void {
|
||||
if (this.staleSessionSweepTimer) {
|
||||
clearInterval(this.staleSessionSweepTimer);
|
||||
this.staleSessionSweepTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweep stale active sessions that have not received any update
|
||||
* within the configured timeout. These are orphaned sessions where
|
||||
* the Stop packet was never received.
|
||||
*/
|
||||
private sweepStaleSessions(): void {
|
||||
const timeoutMs = this.config.staleSessionTimeoutHours * 60 * 60 * 1000;
|
||||
const cutoff = Date.now() - timeoutMs;
|
||||
let swept = 0;
|
||||
|
||||
for (const [sessionId, session] of this.activeSessions) {
|
||||
if (session.lastUpdateTime < cutoff) {
|
||||
session.status = 'terminated';
|
||||
session.terminateCause = 'StaleSessionTimeout';
|
||||
session.endTime = Date.now();
|
||||
session.sessionTime = Math.floor((session.endTime - session.startTime) / 1000);
|
||||
|
||||
if (this.storageManager) {
|
||||
this.archiveSession(session).catch(() => {});
|
||||
}
|
||||
|
||||
this.activeSessions.delete(sessionId);
|
||||
swept++;
|
||||
}
|
||||
}
|
||||
|
||||
if (swept > 0) {
|
||||
logger.log('info', `Swept ${swept} stale RADIUS sessions (no update for ${this.config.staleSessionTimeoutHours}h)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle accounting start request
|
||||
*/
|
||||
|
||||
@@ -183,6 +183,8 @@ export class RadiusServer {
|
||||
this.radiusServer = undefined;
|
||||
}
|
||||
|
||||
this.accountingManager.stop();
|
||||
|
||||
this.running = false;
|
||||
logger.log('info', 'RADIUS server stopped');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user