@@ -31,8 +31,12 @@ const SESSION_SANDBOX_PAUSED_RETENTION_MS = 15 * 60_000
3131
3232const REAP_LOCK_TIMEOUT_MS = 10_000
3333const REAP_CONCURRENCY = 8
34- /** Bounds one sweep's runtime; a backlog drains over the following runs. */
35- const REAP_BATCH_LIMIT = 200
34+ /**
35+ * A sweep starts no kill after this. One kill takes at most the lock wait plus two 5-second
36+ * provider requests, so the sweep reports within the cron route's 60-second limit; the rest waits
37+ * for the next run.
38+ */
39+ const REAP_DEADLINE_MS = 30_000
3640
3741interface SessionAcquisition {
3842 created : CreatedSandbox
@@ -104,26 +108,31 @@ export async function ensureSessionSandbox(args: {
104108export async function reapPausedSessionSandboxes ( ) : Promise < {
105109 killed : number
106110 skipped : number
111+ deferred : number
107112 failed : number
108113} > {
109114 const provider = resolveProvider ( )
110- const result = { killed : 0 , skipped : 0 , failed : 0 }
115+ const result = { killed : 0 , skipped : 0 , deferred : 0 , failed : 0 }
111116 if ( ! provider . listPausedSessionSandboxes || ! provider . killPausedSessionSandbox ) return result
112117 const killPaused = provider . killPausedSessionSandbox . bind ( provider )
113118
119+ const deadline = AbortSignal . timeout ( REAP_DEADLINE_MS )
114120 const pausedBeforeMs = Date . now ( ) - SESSION_SANDBOX_PAUSED_RETENTION_MS
115- const expired = ( await provider . listPausedSessionSandboxes ( ) )
116- . filter ( ( sandbox ) => sandbox . pausedAtMs <= pausedBeforeMs )
117- . slice ( 0 , REAP_BATCH_LIMIT )
118- const outcomes = await mapSettledWithConcurrency ( expired , REAP_CONCURRENCY , ( sandbox ) =>
119- withSandboxSessionLock ( sandbox . sessionKey , AbortSignal . timeout ( REAP_LOCK_TIMEOUT_MS ) , ( ) =>
120- killPaused ( sandbox , pausedBeforeMs )
121- )
121+ const expired = ( await provider . listPausedSessionSandboxes ( ) ) . filter (
122+ ( sandbox ) => sandbox . pausedAtMs <= pausedBeforeMs
122123 )
124+ const outcomes = await mapSettledWithConcurrency ( expired , REAP_CONCURRENCY , async ( sandbox ) => {
125+ if ( deadline . aborted ) return 'deferred' as const
126+ const killed = await withSandboxSessionLock (
127+ sandbox . sessionKey ,
128+ AbortSignal . timeout ( REAP_LOCK_TIMEOUT_MS ) ,
129+ ( ) => killPaused ( sandbox , pausedBeforeMs )
130+ )
131+ return killed ? ( 'killed' as const ) : ( 'skipped' as const )
132+ } )
123133 outcomes . forEach ( ( outcome , index ) => {
124134 if ( outcome . status === 'fulfilled' ) {
125- if ( outcome . value ) result . killed ++
126- else result . skipped ++
135+ result [ outcome . value ] ++
127136 return
128137 }
129138 result . failed ++
0 commit comments