From a4630a9825d26b646f13bcf86973f155629f56bc Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Thu, 16 May 2024 22:39:07 -1000 Subject: [PATCH] feat: code execution time limit --- .../components/chat/Messages/CodeBlock.svelte | 161 ++++++++++++------ static/pyodide-worker.js | 55 ++++++ 2 files changed, 161 insertions(+), 55 deletions(-) create mode 100644 static/pyodide-worker.js diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index c470a4354..9d6f884f7 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -139,73 +139,124 @@ }; const executePython = async (code) => { + if (!code.includes('input')) { + executePythonAsWorker(code); + } else { + result = null; + stdout = null; + stderr = null; + + executing = true; + + let pyodide = await loadPyodide({ + indexURL: '/pyodide/', + stdout: (text) => { + console.log('Python output:', text); + + if (stdout) { + stdout += `${text}\n`; + } else { + stdout = `${text}\n`; + } + }, + stderr: (text) => { + console.log('An error occured:', text); + if (stderr) { + stderr += `${text}\n`; + } else { + stderr = `${text}\n`; + } + } + }); + + try { + const res = await pyodide.loadPackage('micropip'); + console.log(res); + + const micropip = pyodide.pyimport('micropip'); + + await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json'); + + let packages = [ + code.includes('requests') ? 'requests' : null, + code.includes('bs4') ? 'beautifulsoup4' : null, + code.includes('numpy') ? 'numpy' : null, + code.includes('pandas') ? 'pandas' : null + ].filter(Boolean); + + console.log(packages); + await micropip.install(packages); + + result = await pyodide.runPythonAsync(`from js import prompt +def input(p): + return prompt(p) +__builtins__.input = input`); + + result = await pyodide.runPython(code); + + if (!result) { + result = '[NO OUTPUT]'; + } + + console.log(result); + console.log(stdout); + console.log(stderr); + } catch (error) { + console.error('Error:', error); + stderr = error; + } + + executing = false; + } + }; + + const executePythonAsWorker = async (code) => { result = null; stdout = null; stderr = null; executing = true; - let pyodide = await loadPyodide({ - indexURL: '/pyodide/', - stdout: (text) => { - console.log('Python output:', text); + let packages = [ + code.includes('requests') ? 'requests' : null, + code.includes('bs4') ? 'beautifulsoup4' : null, + code.includes('numpy') ? 'numpy' : null, + code.includes('pandas') ? 'pandas' : null + ].filter(Boolean); - if (stdout) { - stdout += `${text}\n`; - } else { - stdout = `${text}\n`; - } - }, - stderr: (text) => { - console.log('An error occured:', text); - if (stderr) { - stderr += `${text}\n`; - } else { - stderr = `${text}\n`; - } - } + const pyodideWorker = new Worker('/pyodide-worker.js'); + + pyodideWorker.postMessage({ + id: id, + code: code, + packages: packages }); - try { - const res = await pyodide.loadPackage('micropip'); - console.log(res); - - // pyodide.setStdin({ stdin: () => prompt() }); - - const micropip = pyodide.pyimport('micropip'); - - await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json'); - - let packages = [ - code.includes('requests') ? 'requests' : null, - code.includes('bs4') ? 'beautifulsoup4' : null, - code.includes('numpy') ? 'numpy' : null, - code.includes('pandas') ? 'pandas' : null - ].filter(Boolean); - - console.log(packages); - await micropip.install(packages); - - result = pyodide.runPython(`from js import prompt -def input(p): - return prompt(p) -__builtins__.input = input`); - - result = pyodide.runPython(code); - - if (!result) { - result = '[NO OUTPUT]'; + setTimeout(() => { + if (executing) { + executing = false; + stderr = 'Execution Time Limit Exceeded'; + pyodideWorker.terminate(); } + }, 10000); - console.log(result); - console.log(stdout); - console.log(stderr); - } catch (error) { - console.error('Error:', error); - stderr = error; - } + pyodideWorker.onmessage = (event) => { + console.log('pyodideWorker.onmessage', event); + const { id, ...data } = event.data; - executing = false; + console.log(id, data); + + data['stdout'] && (stdout = data['stdout']); + data['stderr'] && (stderr = data['stderr']); + data['result'] && (result = data['result']); + + executing = false; + }; + + pyodideWorker.onerror = (event) => { + console.log('pyodideWorker.onerror', event); + executing = false; + }; }; $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : ''; diff --git a/static/pyodide-worker.js b/static/pyodide-worker.js new file mode 100644 index 000000000..cc6b7a9d0 --- /dev/null +++ b/static/pyodide-worker.js @@ -0,0 +1,55 @@ +// webworker.js +// Setup your project to serve `py-worker.js`. You should also serve +// `pyodide.js`, and all its associated `.asm.js`, `.json`, +// and `.wasm` files as well: +importScripts('/pyodide/pyodide.js'); + +async function loadPyodideAndPackages(packages = []) { + self.stdout = null; + self.stderr = null; + self.result = null; + + self.pyodide = await loadPyodide({ + indexURL: '/pyodide/', + stdout: (text) => { + console.log('Python output:', text); + + if (self.stdout) { + self.stdout += `${text}\n`; + } else { + self.stdout = `${text}\n`; + } + }, + stderr: (text) => { + console.log('An error occured:', text); + if (self.stderr) { + self.stderr += `${text}\n`; + } else { + self.stderr = `${text}\n`; + } + } + }); + + await self.pyodide.loadPackage('micropip'); + const micropip = self.pyodide.pyimport('micropip'); + + await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json'); + await micropip.install(packages); +} + +self.onmessage = async (event) => { + const { id, code, ...context } = event.data; + + console.log(event.data) + + // The worker copies the context in its own "memory" (an object mapping name to values) + for (const key of Object.keys(context)) { + self[key] = context[key]; + } + + // make sure loading is done + await loadPyodideAndPackages(self.packages); + + self.result = await self.pyodide.runPythonAsync(code); + self.postMessage({ id, result: self.result, stdout: self.stdout, stderr: self.stderr }); +};