Skip to content

Commit

Permalink
feat: explore the feasibility of running pyodide inside a sharedworker
Browse files Browse the repository at this point in the history
  • Loading branch information
CNSeniorious000 committed Oct 16, 2024
1 parent 61471e7 commit 7fb7afe
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 26 deletions.
10 changes: 5 additions & 5 deletions src/lib/pyodide/start/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import loader from "./loader.py?raw";
import { dev } from "$app/environment";
import { cacheSingleton } from "$lib/utils/cache";
import { getEnv } from "$lib/utils/env";
import { withToast } from "$lib/utils/toast";
import { toast } from "svelte-sonner";
// import { withToast } from "$lib/utils/toast";
// import { toast } from "svelte-sonner";

const getMinimalPyodide = cacheSingleton(withToast({ loading: "loading pyodide runtime" })(async () => {
const getMinimalPyodide = cacheSingleton(async () => {
const { loadPyodide } = await import("pyodide");
const py = await loadPyodide({ indexURL, env: getEnv(), packages: preloadPackages, args: dev ? [] : ["-O"] });
py.globals.set("toast", toast);
// py.globals.set("toast", toast);
return py;
}));
});

const getSetupModule = cacheSingleton(async () => {
const py = await getMinimalPyodide();
Expand Down
14 changes: 14 additions & 0 deletions src/lib/pyodide/worker/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type UUID = ReturnType<Crypto["randomUUID"]>;

interface BaseTask {
id: UUID;
type: string;
data: any;
}

export interface EvalTask extends BaseTask {
type: "eval";
data: string;
}

export type Task = EvalTask;
43 changes: 43 additions & 0 deletions src/lib/pyodide/worker/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable no-restricted-globals */

/// <reference lib="webworker" />

import type { Task } from "./types";

import getPy from "..";

(self as unknown as SharedWorkerGlobalScope).addEventListener("connect", (event) => {
const [port] = event.ports;
port.addEventListener("message", handleMessage);
port.start();
port.postMessage("hello from worker");
});

async function handleMessage(event: MessageEvent<Task | object>) {
if (typeof event.data === "object" && "id" in event.data) {
const port = event.currentTarget as MessagePort;
const { id, type, data } = event.data;
// dispatch
if (type === "eval") {
runTask(id, port, handleEval(data));
}
}
else {
console.warn(event);
}
}

function runTask(id: string, port: MessagePort, promise: Promise<any>) {
promise
.then(result => port.postMessage({ id, result }))
.catch(error => port.postMessage({ id, error }));
}

async function handleEval(source: string) {
// const { loadPyodide: getPy } = await import("pyodide");
// const { getPyodide: getPy } = await import("../start/init");

const py = await getPy();
const result = await py.runPythonAsync(source);
return String(result);
}
33 changes: 33 additions & 0 deletions src/lib/pyodide/worker/workerApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { UUID } from "./types";

import { dev } from "$app/environment";
import { cacheSingleton } from "$lib/utils/cache";

const getWorker = cacheSingleton(() => {
const worker = new SharedWorker(new URL("./worker", import.meta.url), { name: "pyodide", type: /* @vite-ignore */ dev ? "module" : "classic" });
worker.port.addEventListener("message", handleMessage);
worker.port.start();
worker.port.postMessage("hello from main thread");
return worker;
});

const pendingTasks = new Map<UUID, [(value: any) => void, (reason: any) => void]>();

async function handleMessage(event: MessageEvent) {
if (typeof event.data === "object" && "id" in event.data) {
const data: { id: UUID } & ({ result: any } | { error: any }) = event.data;
const [resolve, reject] = pendingTasks.get(data.id)!;
"error" in data ? reject(data.error) : resolve(data.result);
pendingTasks.delete(data.id);
}
}

export function evalPython(source: string) {
const worker = getWorker();
const id = crypto.randomUUID();
const task = new Promise((resolve, reject) => {
pendingTasks.set(id, [resolve, reject]);
});
worker.port.postMessage({ id, data: source, type: "eval" });
return task;
}
20 changes: 1 addition & 19 deletions src/python/common/patches.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import builtins
from functools import cache, wraps
from inspect import Signature, getsource
from inspect import getsource
from os import getenv

import micropip
from js import window
from pyodide.ffi import can_run_sync, run_sync

from .package import get_package_name
Expand Down Expand Up @@ -56,14 +54,6 @@ async def runcode(self: PyodideConsole, source: str, code):
PyodideConsole.runcode = runcode


@cache
def patch_input():
def input(prompt=""):
return window.prompt(str(prompt)) or ""

builtins.input = input


@cache
def patch_sync():
import asyncio
Expand Down Expand Up @@ -95,15 +85,7 @@ def _(duration):
time.sleep = _


@cache
def patch_exit():
window.close.__signature__ = Signature() # type: ignore
builtins.exit = builtins.quit = window.close


patch_install()
patch_linecache()
patch_console()
patch_input()
patch_sync()
patch_exit()
2 changes: 0 additions & 2 deletions src/python/common/toast.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

if TYPE_CHECKING:
from stub import toast
else:
from __main__ import toast


class ToastController:
Expand Down
37 changes: 37 additions & 0 deletions src/routes/test/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import { evalPython } from "$lib/pyodide/worker/workerApi";
let input = "";
let output: any;
let error: any;
$: if (input) {
evalPython(input)
.then((res) => {
output = res;
error = "";
})
.catch((err) => {
error = err;
output = "";
});
}
else {
output = "";
error = "";
}
</script>

<div class="m-10 flex flex-col gap-2.5 font-mono [&>*]:(px-2.5 py-2)">

<input type="text" bind:value={input} class="bg-neutral-8 outline-none ring-(1.2 neutral-4)">

{#if error}
<div class="ws-pre-wrap text-sm text-red">{error}</div>
{/if}

{#if output}
<div class="ws-pre-wrap text-sm text-teal">{output}</div>
{/if}

</div>

0 comments on commit 7fb7afe

Please sign in to comment.