@@ -327,10 +327,15 @@ async function throwIfRunTimedOut(
327327 if ( ! result . timedOut ) return
328328 if ( ! run . lease . session ) throw new DOMException ( 'timeout' , 'AbortError' )
329329 const { outputSandboxDir, sandbox } = run
330+ // Half of what the call has left, so the report lands before the call's deadline.
331+ const listingMs = Math . min (
332+ TIMED_OUT_OUTPUT_LISTING_MS ,
333+ ( getRemainingExecutionMs ( run . signal ) ?? Number . POSITIVE_INFINITY ) / 2
334+ )
330335 const listed = outputSandboxDir
331336 ? await withinDeadline (
332337 ( ) => sandbox . listFiles ( outputSandboxDir , { depth : SANDBOX_OUTPUT_DIR_MAX_DEPTH } ) ,
333- Date . now ( ) + TIMED_OUT_OUTPUT_LISTING_MS ,
338+ Date . now ( ) + listingMs ,
334339 run . signal
335340 ) . catch ( ( ) => [ ] )
336341 : [ ]
@@ -908,21 +913,32 @@ function installBudgetMs(timeoutMs: number): number {
908913 */
909914const MIN_EXPORT_BUDGET_MS = 10_000
910915
916+ /**
917+ * Held back from a workbench run without an export reserve, capped at a quarter of its budget,
918+ * so the run's own timeout answers and reports its progress before the call's deadline.
919+ */
920+ const MAX_TIMEOUT_REPORT_BUDGET_MS = 4_000
921+
911922/**
912923 * The budget handed to user code, less an export reserve when this request will
913924 * read files back. Short budgets are left alone: taking the reserve out of one
914- * would starve the code to buy time for an export it never reaches.
925+ * would starve the code to buy time for an export it never reaches. A workbench
926+ * run, whose timeout reports progress, still keeps a smaller report reserve.
915927 */
916928function codeBudgetMs (
917929 req : { outputSandboxPath ?: string ; outputSandboxPaths ?: string [ ] ; outputSandboxDir ?: string } ,
918- signal : AbortSignal
930+ signal : AbortSignal ,
931+ reportsTimeout : boolean
919932) : number {
920933 const remainingMs = remainingSandboxBudgetMs ( signal )
921934 const exportsFiles = Boolean (
922935 req . outputSandboxDir || req . outputSandboxPath || req . outputSandboxPaths ?. length
923936 )
924- if ( ! exportsFiles || remainingMs <= MIN_EXPORT_BUDGET_MS * 2 ) return remainingMs
925- return remainingMs - MIN_EXPORT_BUDGET_MS
937+ if ( exportsFiles && remainingMs > MIN_EXPORT_BUDGET_MS * 2 ) {
938+ return remainingMs - MIN_EXPORT_BUDGET_MS
939+ }
940+ if ( ! reportsTimeout ) return remainingMs
941+ return remainingMs - Math . min ( MAX_TIMEOUT_REPORT_BUDGET_MS , Math . floor ( remainingMs / 4 ) )
926942}
927943
928944/**
@@ -1060,7 +1076,7 @@ async function executeInSandboxWithinBudget(
10601076 signal ,
10611077 phases ,
10621078 ( verifyCli ) => {
1063- runTimeoutMs = codeBudgetMs ( req , signal )
1079+ runTimeoutMs = codeBudgetMs ( req , signal , Boolean ( lease . session ) )
10641080 return sandbox . runCode ( code , {
10651081 timeoutMs : runTimeoutMs ,
10661082 javascriptPreload : buildJavaScriptRuntimeBindingsSource ( req . runtimeBindings ?? [ ] ) ,
@@ -1283,7 +1299,7 @@ async function executeShellInSandboxWithinBudget(
12831299 signal ,
12841300 phases ,
12851301 ( verifyCli ) => {
1286- runTimeoutMs = codeBudgetMs ( req , signal )
1302+ runTimeoutMs = codeBudgetMs ( req , signal , Boolean ( lease . session ) )
12871303 return sandbox . runCommand ( code , {
12881304 envs : commandEnvironment ,
12891305 timeoutMs : runTimeoutMs ,
0 commit comments