[pyodide] collect output files from worker and include in RunCodeResult

GitOrigin-RevId: fa9d501933ee32729e3d083183cd2a14ff969e95
This commit is contained in:
Domagoj Kriskovic
2026-04-28 08:07:16 +00:00
committed by Copybot
parent 966a2f9bfe
commit 7eee5809bb
4 changed files with 189 additions and 27 deletions
@@ -1,5 +1,6 @@
import type {
OutputStream,
OutputFileData,
ProjectFileData,
PyodideWorkerRequest,
PyodideWorkerResponse,
@@ -20,7 +21,9 @@ export type LifecycleCallback = (
type: 'run-finished'
fileId: string
executionId: string
success: boolean
outputs: string[]
outputFiles: OutputFileData[]
}
) => void
@@ -160,7 +163,9 @@ export class PyodideWorkerClient {
type: 'run-finished',
fileId: response.fileId,
executionId: response.executionId,
success: response.success,
outputs: response.outputs,
outputFiles: response.outputFiles,
})
}
}
@@ -5,6 +5,11 @@ export type ProjectFileData = {
content: string
}
export type OutputFileData = {
relativePath: string
content: Uint8Array
}
// Main thread -> Worker messages
export type InitRequest = {
@@ -48,7 +53,9 @@ export type RunCodeResult = {
type: 'run-code-result'
fileId: string
executionId: string
success: boolean
outputs: string[]
outputFiles: OutputFileData[]
}
export type PyodideWorkerResponse = PyodideWorkerEvent | RunCodeResult
@@ -1,6 +1,8 @@
/// <reference lib="webworker" />
import path from 'path-browserify'
import type { PyodideInterface } from 'pyodide'
import type {
OutputFileData,
ProjectFileData,
PyodideWorkerRequest,
RunCodeRequest,
@@ -98,7 +100,9 @@ async function handleRunCode(msg: RunCodeRequest) {
type: 'run-code-result',
fileId,
executionId,
success: false,
outputs: [],
outputFiles: [],
})
return
}
@@ -134,6 +138,7 @@ async function handleRunCode(msg: RunCodeRequest) {
const fs = instance.FS
const originalWrite = fs.write as PyodideFS['write']
let runError: unknown = null
try {
if (msg.files.length > 0) {
syncProjectFiles(fs, msg.files)
@@ -162,7 +167,14 @@ async function handleRunCode(msg: RunCodeRequest) {
executionId,
})
}
} catch (runError) {
} catch (e) {
runError = e
}
fs.write = originalWrite
const paths = [...writtenPaths]
if (runError) {
const errorMessage =
runError instanceof Error ? runError.message : String(runError)
@@ -173,16 +185,43 @@ async function handleRunCode(msg: RunCodeRequest) {
fileId,
executionId,
})
} finally {
fs.write = originalWrite
const outputs = [...writtenPaths].sort()
self.postMessage({
type: 'run-code-result',
fileId,
executionId,
outputs,
success: false,
outputs: paths,
outputFiles: [],
})
return
}
const outputFiles: OutputFileData[] = []
const transferables: Transferable[] = []
for (const writtenPath of paths) {
const content = fs.readFile(writtenPath)
const relativePath = writtenPath.slice(PROJECT_FS_PREFIX.length)
outputFiles.push({ relativePath, content })
if (content.buffer instanceof ArrayBuffer) {
transferables.push(content.buffer)
}
}
// The transferables moves ownership of each ArrayBuffer to the main thread
// instead of structured-cloning it. The buffers are already referenced from
// outputFiles.content; listing them here just swaps copy for move, so file
// contents travel through once rather than being allocated on both sides.
self.postMessage(
{
type: 'run-code-result',
fileId,
executionId,
success: true,
outputs: paths,
outputFiles,
},
transferables
)
}
self.addEventListener('message', async event => {