[web] Add stop button for Python code execution via worker termination

GitOrigin-RevId: f44b429a1d85e66ce89719817efd7acbfc7c4540
This commit is contained in:
Domagoj Kriskovic
2026-04-13 08:04:07 +00:00
committed by Copybot
parent 50db96878d
commit 19c8434653
7 changed files with 170 additions and 23 deletions
@@ -32,7 +32,7 @@ class WorkerMock {
emitMessage(message: unknown) {
for (const listener of this.messageListeners) {
listener({ data: message } as MessageEvent)
listener({ data: message, target: this } as unknown as MessageEvent)
}
}
}
@@ -153,4 +153,83 @@ describe('PyodideWorkerClient', function () {
expect(worker.terminated).to.equal(true)
})
describe('stop', function () {
it('terminates the current worker and creates a new one', function () {
const client = new PyodideWorkerClient({
baseAssetPath: BASE_ASSET_PATH,
})
const originalWorker = WorkerMock.instances[0]
originalWorker.emitMessage({ type: 'listening' })
originalWorker.emitMessage({ type: 'loaded' })
client.stop()
expect(originalWorker.terminated).to.equal(true)
expect(WorkerMock.instances).to.have.length(2)
})
it('sends init to the new worker once it reports listening', function () {
const client = new PyodideWorkerClient({
baseAssetPath: BASE_ASSET_PATH,
})
const originalWorker = WorkerMock.instances[0]
originalWorker.emitMessage({ type: 'listening' })
originalWorker.emitMessage({ type: 'loaded' })
client.stop()
const newWorker = WorkerMock.instances[1]
expect(newWorker.postedMessages).to.have.length(0)
newWorker.emitMessage({ type: 'listening' })
expect(newWorker.postedMessages).to.deep.equal([
{ type: 'init', baseAssetPath: BASE_ASSET_PATH },
])
})
it('allows running code on the new worker after stop', function () {
const client = new PyodideWorkerClient({
baseAssetPath: BASE_ASSET_PATH,
})
const originalWorker = WorkerMock.instances[0]
originalWorker.emitMessage({ type: 'listening' })
originalWorker.emitMessage({ type: 'loaded' })
client.stop()
const newWorker = WorkerMock.instances[1]
newWorker.emitMessage({ type: 'listening' })
newWorker.emitMessage({ type: 'loaded' })
client.runCode('print("after stop")', {
requestId: 'main.py',
files: [],
})
const runRequest = newWorker.postedMessages.find(
(message: any) => message.type === 'run-code'
)
expect(runRequest).to.include({
type: 'run-code',
id: 'main.py',
code: 'print("after stop")',
})
})
it('is a no-op after destroy', function () {
const client = new PyodideWorkerClient({
baseAssetPath: BASE_ASSET_PATH,
})
const originalWorker = WorkerMock.instances[0]
originalWorker.emitMessage({ type: 'listening' })
originalWorker.emitMessage({ type: 'loaded' })
client.destroy()
client.stop()
// No new worker should have been created
expect(WorkerMock.instances).to.have.length(1)
})
})
})