@@ -64,17 +64,25 @@ class SessionManager {
6464 }
6565
6666 /**
67- * Unregister a session from tracking (called on final cleanup)
67+ * Unregister a session from tracking (called on final cleanup).
68+ * Pass the proxy to record session duration before it is closed.
6869 */
69- unregisterSession ( sessionId : string | undefined ) : void {
70+ unregisterSession ( sessionId : string | undefined , proxy ?: TranscriberProxy ) : void {
7071 if ( ! sessionId ) return ;
7172 const wasActive = this . activeSessions . has ( sessionId ) ;
7273 this . activeSessions . delete ( sessionId ) ;
7374
74- // Metrics: decrement active if it was tracked
75+ // Metrics: decrement active if it was tracked, record duration
7576 if ( wasActive ) {
7677 const instruments = getInstruments ( ) ;
7778 instruments . sessionsActive . add ( - 1 ) ;
79+
80+ if ( proxy ) {
81+ const durationSec = proxy . getSessionDurationSec ( ) ;
82+ if ( durationSec > 0 ) {
83+ instruments . sessionDurationSeconds . record ( durationSec ) ;
84+ }
85+ }
7886 }
7987
8088 logger . debug ( `Session ${ sessionId } unregistered from active sessions (total: ${ this . activeSessions . size } )` ) ;
@@ -91,10 +99,16 @@ class SessionManager {
9199 return ;
92100 }
93101
102+ if ( ! this . activeSessions . has ( sessionId ) ) {
103+ // Session was already unregistered (e.g. by a prior error handler) — don't detach
104+ logger . debug ( `Session ${ sessionId } not in active sessions, skipping detach` ) ;
105+ return ;
106+ }
107+
94108 if ( ! config . sessionResumeEnabled ) {
95109 // Resume disabled - close immediately
96110 logger . debug ( `Session resumption disabled, closing session ${ sessionId } immediately` ) ;
97- this . unregisterSession ( sessionId ) ;
111+ this . unregisterSession ( sessionId , proxy ) ;
98112 proxy . close ( ) ;
99113 return ;
100114 }
@@ -204,19 +218,35 @@ class SessionManager {
204218 shutdown ( ) : void {
205219 const detachedCount = this . detachedSessions . size ;
206220 const activeCount = this . activeSessions . size ;
221+ const instruments = getInstruments ( ) ;
207222
208223 logger . info ( `Shutting down SessionManager (detached: ${ detachedCount } , active: ${ activeCount } )` ) ;
209224
210225 // Clean up all detached sessions
211226 for ( const [ sessionId , session ] of this . detachedSessions ) {
212227 logger . info ( `Cleaning up detached session ${ sessionId } during shutdown` ) ;
213228 clearTimeout ( session . gracePeriodTimer ) ;
229+
230+ const durationSec = session . transcriberProxy . getSessionDurationSec ( ) ;
231+ if ( durationSec > 0 ) {
232+ instruments . sessionDurationSeconds . record ( durationSec ) ;
233+ }
234+ instruments . sessionsDetached . add ( - 1 ) ;
235+
214236 session . transcriberProxy . close ( ) ;
215237 }
216238 this . detachedSessions . clear ( ) ;
217239
218- // Note: Active sessions will be cleaned up by server.ts through normal close handlers
219- // Just clear our tracking
240+ // Record duration for active sessions before clearing.
241+ // proxy.close() is intentionally not called here — server.ts close handlers
242+ // will fire when the HTTP server shuts down and handle connection teardown.
243+ for ( const proxy of this . activeSessions . values ( ) ) {
244+ const durationSec = proxy . getSessionDurationSec ( ) ;
245+ if ( durationSec > 0 ) {
246+ instruments . sessionDurationSeconds . record ( durationSec ) ;
247+ }
248+ instruments . sessionsActive . add ( - 1 ) ;
249+ }
220250 this . activeSessions . clear ( ) ;
221251
222252 logger . info ( 'SessionManager shutdown complete' ) ;
0 commit comments