11import { emitDiagnosticEvent } from "../infra/diagnostic-events.js" ;
2+ import {
3+ diagnosticSessionStates ,
4+ getDiagnosticSessionState ,
5+ getDiagnosticSessionStateCountForTest as getDiagnosticSessionStateCountForTestImpl ,
6+ pruneDiagnosticSessionStates ,
7+ resetDiagnosticSessionStateForTest ,
8+ type SessionRef ,
9+ type SessionStateValue ,
10+ } from "./diagnostic-session-state.js" ;
211import { createSubsystemLogger } from "./subsystem.js" ;
312
413const diag = createSubsystemLogger ( "diagnostic" ) ;
514
6- type SessionStateValue = "idle" | "processing" | "waiting" ;
7-
8- type SessionState = {
9- sessionId ?: string ;
10- sessionKey ?: string ;
11- lastActivity : number ;
12- state : SessionStateValue ;
13- queueDepth : number ;
14- } ;
15-
16- type SessionRef = {
17- sessionId ?: string ;
18- sessionKey ?: string ;
19- } ;
20-
21- const sessionStates = new Map < string , SessionState > ( ) ;
22- const SESSION_STATE_TTL_MS = 30 * 60 * 1000 ;
23- const SESSION_STATE_PRUNE_INTERVAL_MS = 60 * 1000 ;
24- const SESSION_STATE_MAX_ENTRIES = 2000 ;
25-
2615const webhookStats = {
2716 received : 0 ,
2817 processed : 0 ,
@@ -31,72 +20,11 @@ const webhookStats = {
3120} ;
3221
3322let lastActivityAt = 0 ;
34- let lastSessionPruneAt = 0 ;
3523
3624function markActivity ( ) {
3725 lastActivityAt = Date . now ( ) ;
3826}
3927
40- function pruneSessionStates ( now = Date . now ( ) , force = false ) : void {
41- const shouldPruneForSize = sessionStates . size > SESSION_STATE_MAX_ENTRIES ;
42- if ( ! force && ! shouldPruneForSize && now - lastSessionPruneAt < SESSION_STATE_PRUNE_INTERVAL_MS ) {
43- return ;
44- }
45- lastSessionPruneAt = now ;
46-
47- for ( const [ key , state ] of sessionStates . entries ( ) ) {
48- const ageMs = now - state . lastActivity ;
49- const isIdle = state . state === "idle" ;
50- if ( isIdle && state . queueDepth <= 0 && ageMs > SESSION_STATE_TTL_MS ) {
51- sessionStates . delete ( key ) ;
52- }
53- }
54-
55- if ( sessionStates . size <= SESSION_STATE_MAX_ENTRIES ) {
56- return ;
57- }
58- const excess = sessionStates . size - SESSION_STATE_MAX_ENTRIES ;
59- const ordered = Array . from ( sessionStates . entries ( ) ) . toSorted (
60- ( a , b ) => a [ 1 ] . lastActivity - b [ 1 ] . lastActivity ,
61- ) ;
62- for ( let i = 0 ; i < excess ; i += 1 ) {
63- const key = ordered [ i ] ?. [ 0 ] ;
64- if ( ! key ) {
65- break ;
66- }
67- sessionStates . delete ( key ) ;
68- }
69- }
70-
71- function resolveSessionKey ( { sessionKey, sessionId } : SessionRef ) {
72- return sessionKey ?? sessionId ?? "unknown" ;
73- }
74-
75- function getSessionState ( ref : SessionRef ) : SessionState {
76- pruneSessionStates ( ) ;
77- const key = resolveSessionKey ( ref ) ;
78- const existing = sessionStates . get ( key ) ;
79- if ( existing ) {
80- if ( ref . sessionId ) {
81- existing . sessionId = ref . sessionId ;
82- }
83- if ( ref . sessionKey ) {
84- existing . sessionKey = ref . sessionKey ;
85- }
86- return existing ;
87- }
88- const created : SessionState = {
89- sessionId : ref . sessionId ,
90- sessionKey : ref . sessionKey ,
91- lastActivity : Date . now ( ) ,
92- state : "idle" ,
93- queueDepth : 0 ,
94- } ;
95- sessionStates . set ( key , created ) ;
96- pruneSessionStates ( Date . now ( ) , true ) ;
97- return created ;
98- }
99-
10028export function logWebhookReceived ( params : {
10129 channel : string ;
10230 updateType ?: string ;
@@ -174,7 +102,7 @@ export function logMessageQueued(params: {
174102 channel ?: string ;
175103 source : string ;
176104} ) {
177- const state = getSessionState ( params ) ;
105+ const state = getDiagnosticSessionState ( params ) ;
178106 state . queueDepth += 1 ;
179107 state . lastActivity = Date . now ( ) ;
180108 if ( diag . isEnabled ( "debug" ) ) {
@@ -244,7 +172,7 @@ export function logSessionStateChange(
244172 reason ?: string ;
245173 } ,
246174) {
247- const state = getSessionState ( params ) ;
175+ const state = getDiagnosticSessionState ( params ) ;
248176 const isProbeSession = state . sessionId ?. startsWith ( "probe-" ) ?? false ;
249177 const prevState = state . state ;
250178 state . state = params . state ;
@@ -274,7 +202,7 @@ export function logSessionStateChange(
274202}
275203
276204export function logSessionStuck ( params : SessionRef & { state : SessionStateValue ; ageMs : number } ) {
277- const state = getSessionState ( params ) ;
205+ const state = getDiagnosticSessionState ( params ) ;
278206 diag . warn (
279207 `stuck session: sessionId=${ state . sessionId ?? "unknown" } sessionKey=${
280208 state . sessionKey ?? "unknown"
@@ -329,7 +257,7 @@ export function logRunAttempt(params: SessionRef & { runId: string; attempt: num
329257}
330258
331259export function logActiveRuns ( ) {
332- const activeSessions = Array . from ( sessionStates . entries ( ) )
260+ const activeSessions = Array . from ( diagnosticSessionStates . entries ( ) )
333261 . filter ( ( [ , s ] ) => s . state === "processing" )
334262 . map (
335263 ( [ id , s ] ) =>
@@ -347,14 +275,14 @@ export function startDiagnosticHeartbeat() {
347275 }
348276 heartbeatInterval = setInterval ( ( ) => {
349277 const now = Date . now ( ) ;
350- pruneSessionStates ( now , true ) ;
351- const activeCount = Array . from ( sessionStates . values ( ) ) . filter (
278+ pruneDiagnosticSessionStates ( now , true ) ;
279+ const activeCount = Array . from ( diagnosticSessionStates . values ( ) ) . filter (
352280 ( s ) => s . state === "processing" ,
353281 ) . length ;
354- const waitingCount = Array . from ( sessionStates . values ( ) ) . filter (
282+ const waitingCount = Array . from ( diagnosticSessionStates . values ( ) ) . filter (
355283 ( s ) => s . state === "waiting" ,
356284 ) . length ;
357- const totalQueued = Array . from ( sessionStates . values ( ) ) . reduce (
285+ const totalQueued = Array . from ( diagnosticSessionStates . values ( ) ) . reduce (
358286 ( sum , s ) => sum + s . queueDepth ,
359287 0 ,
360288 ) ;
@@ -386,7 +314,7 @@ export function startDiagnosticHeartbeat() {
386314 queued : totalQueued ,
387315 } ) ;
388316
389- for ( const [ , state ] of sessionStates ) {
317+ for ( const [ , state ] of diagnosticSessionStates ) {
390318 const ageMs = now - state . lastActivity ;
391319 if ( state . state === "processing" && ageMs > 120_000 ) {
392320 logSessionStuck ( {
@@ -409,17 +337,16 @@ export function stopDiagnosticHeartbeat() {
409337}
410338
411339export function getDiagnosticSessionStateCountForTest ( ) : number {
412- return sessionStates . size ;
340+ return getDiagnosticSessionStateCountForTestImpl ( ) ;
413341}
414342
415343export function resetDiagnosticStateForTest ( ) : void {
416- sessionStates . clear ( ) ;
344+ resetDiagnosticSessionStateForTest ( ) ;
417345 webhookStats . received = 0 ;
418346 webhookStats . processed = 0 ;
419347 webhookStats . errors = 0 ;
420348 webhookStats . lastReceived = 0 ;
421349 lastActivityAt = 0 ;
422- lastSessionPruneAt = 0 ;
423350 stopDiagnosticHeartbeat ( ) ;
424351}
425352
0 commit comments