From 3ccf69e10a7215159196157b85e38c23f07af253 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Sun, 27 Jul 2025 22:02:19 +1000 Subject: [PATCH] Dont cache code, may need to revert --- deno_server.ts | 67 ++++++++++++++++++++++++++++++++++++++++---------- worker.ts | 16 +++++------- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/deno_server.ts b/deno_server.ts index 56fcb8e..7da90a5 100644 --- a/deno_server.ts +++ b/deno_server.ts @@ -6,13 +6,13 @@ const States = Object.freeze({ TIMEOUT: "TIMEOUT", }); -async function handler(req: Request): Promise { - if (req.method !== "POST" || new URL(req.url).pathname !== "/execute") { - return new Response("Not Found", { status: 404 }); - } +// Create a pool of workers. +const workerPool: { worker: Worker; inUse: boolean }[] = []; +// Allow the number of workers to be configured via an environment variable. +const numWorkers = + parseInt(Deno.env.get("NUM_WORKERS") || "0") || navigator.hardwareConcurrency; - // For each request, spawn a new worker, use it, and then terminate it. - // This ensures a clean, stateless environment for every execution. +for (let i = 0; i < numWorkers; i++) { const worker = new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module", deno: { @@ -20,6 +20,47 @@ async function handler(req: Request): Promise { permissions: "inherit", }, }); + workerPool.push({ worker, inUse: false }); +} + +const requestQueue: (( + value: + | { worker: Worker; inUse: boolean } + | PromiseLike<{ worker: Worker; inUse: boolean }> +) => void)[] = []; + +function getAvailableWorker() { + return new Promise((resolve) => { + const availableWorker = workerPool.find((w) => !w.inUse); + if (availableWorker) { + availableWorker.inUse = true; + resolve(availableWorker); + } else { + requestQueue.push(resolve); + } + }); +} + +function releaseWorker(worker) { + if (requestQueue.length > 0) { + const nextRequest = requestQueue.shift(); + if (nextRequest) { + nextRequest(worker); + } + } else { + worker.inUse = false; + } +} + +async function handler(req: Request): Promise { + if (req.method !== "POST" || new URL(req.url).pathname !== "/execute") { + return new Response("Not Found", { status: 404 }); + } + + const availableWorker = (await getAvailableWorker()) as { + worker: Worker; + inUse: boolean; + }; try { const body = await req.json(); @@ -34,8 +75,8 @@ async function handler(req: Request): Promise { let timeoutId: number; const cleanup = () => { - worker.removeEventListener("message", messageHandler); - worker.removeEventListener("error", errorHandler); + availableWorker.worker.removeEventListener("message", messageHandler); + availableWorker.worker.removeEventListener("error", errorHandler); if (timeoutId) clearTimeout(timeoutId); }; @@ -54,10 +95,10 @@ async function handler(req: Request): Promise { reject(new Error("Timeout")); }, TIMEOUT_MS); - worker.addEventListener("message", messageHandler); - worker.addEventListener("error", errorHandler); + availableWorker.worker.addEventListener("message", messageHandler); + availableWorker.worker.addEventListener("error", errorHandler); - worker.postMessage({ code, request, environment, name }); + availableWorker.worker.postMessage({ code, request, environment, name }); }); const startTime = performance.now(); @@ -83,12 +124,12 @@ async function handler(req: Request): Promise { } catch (e) { return new Response(`Bad Request: ${e.message}`, { status: 400 }); } finally { - worker.terminate(); // Ensure the worker is always terminated. + releaseWorker(availableWorker); // Release the worker. } } const port = parseInt(Deno.env.get("PORT") || "8000"); -console.log(`⚡ Deno server ready on :${port}`); +console.log(`⚡ Deno server ready on :${port} with ${numWorkers} workers.`); await serve(handler, { port }); diff --git a/worker.ts b/worker.ts index eea83f5..7c9b98d 100644 --- a/worker.ts +++ b/worker.ts @@ -1,5 +1,5 @@ // isolator/worker.ts -const moduleCache = new Map(); +// No module cache to ensure fresh code for every execution. const States = Object.freeze({ SUCCESS: "SUCCESS", @@ -52,15 +52,11 @@ self.onmessage = async (e) => { status ); - let userModule = moduleCache.get(code); - if (!userModule) { - // Use a data URL to import the user's code as an ES module. - const dataUrl = `data:text/javascript;base64,${btoa( - unescape(encodeURIComponent(code)) - )}`; - userModule = await import(dataUrl); - moduleCache.set(code, userModule); - } + // Use a data URL to import the user's code as a fresh ES module on every execution. + const dataUrl = `data:text/javascript;base64,${btoa( + unescape(encodeURIComponent(code)) + )}`; + const userModule = await import(dataUrl); if (typeof userModule.default !== "function") { throw new Error(