@@ -497,29 +497,38 @@ const ABANDONED_RUN_IDLE_MS = 60_000
497497 * no run is provably abandoned.
498498 */
499499export async function findAbandonedRecoverableRuns ( limit : number ) : Promise < UnownedRun [ ] > {
500- const candidates : UnownedRun [ ] = await db
501- . select ( unownedRunColumns )
502- . from ( copilotRuns )
503- . where (
504- and (
505- inArray ( copilotRuns . status , UNFINISHED_RUN_STATUSES ) ,
506- isNotNull ( controllerToken ) ,
507- sql `${ copilotRuns . requestContext } ->'recovery'->>'kind' = 'interactive_stream'` ,
508- idleFor ( ABANDONED_RUN_IDLE_MS ) ,
509- not ( idleFor ( ORPHANED_RUN_GRACE_MS ) ) ,
510- not ( stopRequested )
500+ const abandoned : UnownedRun [ ] = [ ]
501+ let after : string | undefined
502+ /** Pages past runs whose controller is alive, so they never crowd out an abandoned one. */
503+ while ( abandoned . length < limit ) {
504+ const candidates : UnownedRun [ ] = await db
505+ . select ( unownedRunColumns )
506+ . from ( copilotRuns )
507+ . where (
508+ and (
509+ inArray ( copilotRuns . status , UNFINISHED_RUN_STATUSES ) ,
510+ isNotNull ( controllerToken ) ,
511+ sql `${ copilotRuns . requestContext } ->'recovery'->>'kind' = 'interactive_stream'` ,
512+ idleFor ( ABANDONED_RUN_IDLE_MS ) ,
513+ not ( idleFor ( ORPHANED_RUN_GRACE_MS ) ) ,
514+ not ( stopRequested ) ,
515+ after ? gt ( copilotRuns . id , after ) : undefined
516+ )
511517 )
512- )
513- . orderBy ( asc ( copilotRuns . updatedAt ) )
514- . limit ( limit )
515- if ( candidates . length === 0 ) return [ ]
516- try {
517- const locked = await findStreamsHoldingChatLock ( candidates )
518- return candidates . filter ( ( run ) => ! locked . has ( run . streamId ) )
519- } catch ( error ) {
520- logger . warn ( 'Chat stream ownership is unreadable; no run is taken over this round' , {
521- error : getErrorMessage ( error ) ,
522- } )
523- return [ ]
518+ . orderBy ( asc ( copilotRuns . id ) )
519+ . limit ( SWEEP_BATCH_SIZE )
520+ if ( candidates . length === 0 ) break
521+ after = candidates [ candidates . length - 1 ] . id
522+ try {
523+ const locked = await findStreamsHoldingChatLock ( candidates )
524+ abandoned . push ( ...candidates . filter ( ( run ) => ! locked . has ( run . streamId ) ) )
525+ } catch ( error ) {
526+ logger . warn ( 'Chat stream ownership is unreadable; no run is taken over this round' , {
527+ error : getErrorMessage ( error ) ,
528+ } )
529+ return [ ]
530+ }
531+ if ( candidates . length < SWEEP_BATCH_SIZE ) break
524532 }
533+ return abandoned . slice ( 0 , limit )
525534}
0 commit comments