Enhance session handling in SessionRoutes

- Improved logging for session aborts and unexpected exits.
- Introduced a variable to track if the session was aborted for clarity.
- Added logic to create a new AbortController when restarting the generator after a crash.
- Implemented a mechanism to abort the session if there are no pending tasks after a natural completion.
- Ensured that errors during recovery checks lead to session abortion to prevent resource leaks.
This commit is contained in:
Alex Newman
2025-12-31 16:49:17 -05:00
parent fef332d213
commit de20eb65b5
2 changed files with 78 additions and 66 deletions
File diff suppressed because one or more lines are too long
@@ -168,8 +168,9 @@ export class SessionRoutes extends BaseRouteHandler {
}) })
.finally(() => { .finally(() => {
const sessionDbId = session.sessionDbId; const sessionDbId = session.sessionDbId;
const wasAborted = session.abortController.signal.aborted;
if (session.abortController.signal.aborted) {
if (wasAborted) {
logger.info('SESSION', `Generator aborted`, { sessionId: sessionDbId }); logger.info('SESSION', `Generator aborted`, { sessionId: sessionDbId });
} else { } else {
logger.warn('SESSION', `Generator exited unexpectedly`, { sessionId: sessionDbId }); logger.warn('SESSION', `Generator exited unexpectedly`, { sessionId: sessionDbId });
@@ -180,16 +181,20 @@ export class SessionRoutes extends BaseRouteHandler {
this.workerService.broadcastProcessingStatus(); this.workerService.broadcastProcessingStatus();
// Crash recovery: If not aborted and still has work, restart // Crash recovery: If not aborted and still has work, restart
if (!session.abortController.signal.aborted) { if (!wasAborted) {
try { try {
const pendingStore = this.sessionManager.getPendingMessageStore(); const pendingStore = this.sessionManager.getPendingMessageStore();
const pendingCount = pendingStore.getPendingCount(sessionDbId); const pendingCount = pendingStore.getPendingCount(sessionDbId);
if (pendingCount > 0) { if (pendingCount > 0) {
logger.info('SESSION', `Restarting generator after crash/exit with pending work`, { logger.info('SESSION', `Restarting generator after crash/exit with pending work`, {
sessionId: sessionDbId, sessionId: sessionDbId,
pendingCount pendingCount
}); });
// Create new AbortController for the restarted generator
session.abortController = new AbortController();
// Small delay before restart // Small delay before restart
setTimeout(() => { setTimeout(() => {
const stillExists = this.sessionManager.getSession(sessionDbId); const stillExists = this.sessionManager.getSession(sessionDbId);
@@ -197,12 +202,19 @@ export class SessionRoutes extends BaseRouteHandler {
this.startGeneratorWithProvider(stillExists, this.getSelectedProvider(), 'crash-recovery'); this.startGeneratorWithProvider(stillExists, this.getSelectedProvider(), 'crash-recovery');
} }
}, 1000); }, 1000);
} else {
// No pending work - abort to kill the child process
session.abortController.abort();
logger.debug('SESSION', 'Aborted controller after natural completion', {
sessionId: sessionDbId
});
} }
} catch (e) { } catch (e) {
// Ignore errors during recovery check // Ignore errors during recovery check, but still abort to prevent leaks
session.abortController.abort();
} }
} }
// NOTE: We do NOT delete the session here anymore. // NOTE: We do NOT delete the session here anymore.
// The generator waits for events, so if it exited, it's either aborted or crashed. // The generator waits for events, so if it exited, it's either aborted or crashed.
// Idle sessions stay in memory (ActiveSession is small) to listen for future events. // Idle sessions stay in memory (ActiveSession is small) to listen for future events.
}); });