Skip to content

Commit 1bfe3f3

Browse files
committed
fix(sandbox): drop in-flight fetches when the isolate reports a timeout
1 parent 248c12b commit 1bfe3f3

2 files changed

Lines changed: 25 additions & 13 deletions

File tree

‎apps/sim/lib/execution/isolated-vm.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,9 +871,12 @@ function handleWorkerMessage(workerId: number, message: unknown) {
871871
// If the caller aborted, the outer Promise is already resolved with
872872
// AbortError. Still run all the bookkeeping above so pool counters stay
873873
// accurate; just skip re-resolving.
874+
const result = msg.result as IsolatedVMExecutionResult
874875
if (!pending.abort.signal.aborted) {
875-
pending.resolve(msg.result as IsolatedVMExecutionResult)
876+
pending.resolve(result)
876877
}
878+
// The isolate's own timeout gives up on the execution just as the pool's does.
879+
if (result.termination === 'timeout') pending.abort.abort()
877880
drainQueue()
878881
}
879882
return

‎apps/sim/scripts/test-isolated-vm-remote-e2e.ts‎

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -541,33 +541,42 @@ async function run() {
541541

542542
const checkFetchDropped = (
543543
name: string,
544-
endExecution: (server: StalledServer) => Promise<void>
544+
endExecution: (server: StalledServer) => Promise<void>,
545+
withinMs?: number
545546
) =>
546547
check(name, async () => {
547548
const server = await stalledServer()
548549
try {
549550
await endExecution(server)
550-
assert(await server.droppedSoon(), 'The app kept the fetch open')
551+
assert(await server.droppedSoon(withinMs), 'The app kept the fetch open')
551552
} finally {
552553
server.close()
553554
}
554555
})
555556

556-
await check('drops the in-flight fetch of a timed-out execution at the timeout', async () => {
557-
const server = await stalledServer()
558-
try {
557+
await checkFetchDropped(
558+
'drops the in-flight fetch of an execution the app timed out',
559+
async (server) => {
559560
// Started late in the run, so only dropping it at the timeout, not at its own time
560-
// budget, closes it promptly.
561+
// budget, closes it within the grace period.
561562
const result = await execute(
562-
`await fetch('http://127.0.0.1:${slowAddress.port}/?ms=800'); await fetch('${server.url}')`,
563+
`await fetch('http://127.0.0.1:${slowAddress.port}/?ms=800'); fetch('${server.url}'); await new Promise(() => {})`,
563564
{ timeoutMs: 1000 }
564565
)
565566
assert.equal(result.termination, 'timeout', result.error?.message)
566-
assert(await server.droppedSoon(400), 'The timed-out execution kept its fetch open')
567-
} finally {
568-
server.close()
569-
}
570-
})
567+
},
568+
400
569+
)
570+
571+
await checkFetchDropped(
572+
'drops the in-flight fetch of an execution the isolate timed out',
573+
async (server) => {
574+
// The isolate's own timeout ends a synchronous spin a second before the pool would.
575+
const result = await execute(`fetch('${server.url}'); for (;;) {}`, { timeoutMs: 1000 })
576+
assert.equal(result.termination, 'timeout', result.error?.message)
577+
},
578+
400
579+
)
571580

572581
await checkFetchDropped('drops the in-flight fetch of a cancelled execution', async (server) => {
573582
const controller = new AbortController()

0 commit comments

Comments
 (0)