From c3b8d8b4ba7e9b8d6008f0e2b004a4c5df35a632 Mon Sep 17 00:00:00 2001 From: Yongteng Lei Date: Wed, 23 Jul 2025 09:26:09 +0800 Subject: [PATCH] Refa: improve usability of Node.js/JavaScript code executor (#8979) ### What problem does this PR solve? Improve usability of Node.js/JavaScript code executor. ### Type of change - [x] Refactoring --------- Co-authored-by: Kevin Hu Co-authored-by: writinwaters <93570324+writinwaters@users.noreply.github.com> --- sandbox/README.md | 36 +++++++++++++++++++ sandbox/executor_manager/api/handlers.py | 9 +++-- sandbox/executor_manager/api/routes.py | 3 +- sandbox/executor_manager/core/container.py | 4 +-- .../executor_manager/services/execution.py | 36 ++++++++++++++----- 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/sandbox/README.md b/sandbox/README.md index 2e4e209ff..6384e7016 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -213,6 +213,42 @@ To add Node.js dependencies: --- + +## Usage + +### ๐Ÿ A Python example + +```python +def main(arg1: str, arg2: str) -> str: + return f"result: {arg1 + arg2}" +``` + +### ๐ŸŸจ JavaScript examples + +A simple sync function + +```javascript +function main({arg1, arg2}) { + return arg1+arg2 +} +``` + +Async funcion with aioxs + +```javascript +const axios = require('axios'); +async function main() { + try { + const response = await axios.get('https://github.com/infiniflow/ragflow'); + return 'Body:' + response.data; + } catch (error) { + return 'Error:' + error.message; + } +} +``` + +--- + ## ๐Ÿ“‹ FAQ ### โ“Sandbox Not Working? diff --git a/sandbox/executor_manager/api/handlers.py b/sandbox/executor_manager/api/handlers.py index a71d10381..9478a0bfd 100644 --- a/sandbox/executor_manager/api/handlers.py +++ b/sandbox/executor_manager/api/handlers.py @@ -15,24 +15,29 @@ # import base64 +from core.container import _CONTAINER_EXECUTION_SEMAPHORES from core.logger import logger from fastapi import Request -from models.enums import ResultStatus +from models.enums import ResultStatus, SupportLanguage from models.schemas import CodeExecutionRequest, CodeExecutionResult from services.execution import execute_code from services.limiter import limiter from services.security import analyze_code_security -from core.container import _CONTAINER_EXECUTION_SEMAPHORES + async def healthz_handler(): return {"status": "ok"} + @limiter.limit("5/second") async def run_code_handler(req: CodeExecutionRequest, request: Request): logger.info("๐ŸŸข Received /run request") async with _CONTAINER_EXECUTION_SEMAPHORES[req.language]: code = base64.b64decode(req.code_b64).decode("utf-8") + if req.language == SupportLanguage.NODEJS: + code += "\n\nmodule.exports = { main };" + req.code_b64 = base64.b64encode(code.encode("utf-8")).decode("utf-8") is_safe, issues = analyze_code_security(code, language=req.language) if not is_safe: issue_details = "\n".join([f"Line {lineno}: {issue}" for issue, lineno in issues]) diff --git a/sandbox/executor_manager/api/routes.py b/sandbox/executor_manager/api/routes.py index e43a45cc3..3a338a6a4 100644 --- a/sandbox/executor_manager/api/routes.py +++ b/sandbox/executor_manager/api/routes.py @@ -20,4 +20,5 @@ from api.handlers import healthz_handler, run_code_handler router = APIRouter() router.get("/healthz")(healthz_handler) -router.post("/run")(run_code_handler) \ No newline at end of file +router.post("/run")(run_code_handler) + diff --git a/sandbox/executor_manager/core/container.py b/sandbox/executor_manager/core/container.py index 30632e5c2..f953886c1 100644 --- a/sandbox/executor_manager/core/container.py +++ b/sandbox/executor_manager/core/container.py @@ -26,7 +26,7 @@ from core.logger import logger _CONTAINER_QUEUES: dict[SupportLanguage, Queue] = {} _CONTAINER_LOCK: asyncio.Lock = asyncio.Lock() -_CONTAINER_EXECUTION_SEMAPHORES:dict[SupportLanguage,asyncio.Semaphore] = {} +_CONTAINER_EXECUTION_SEMAPHORES: dict[SupportLanguage, asyncio.Semaphore] = {} async def init_containers(size: int) -> tuple[int, int]: @@ -38,7 +38,7 @@ async def init_containers(size: int) -> tuple[int, int]: _CONTAINER_QUEUES[SupportLanguage.PYTHON].get_nowait() while not _CONTAINER_QUEUES[SupportLanguage.NODEJS].empty(): _CONTAINER_QUEUES[SupportLanguage.NODEJS].get_nowait() - + for language in SupportLanguage: _CONTAINER_EXECUTION_SEMAPHORES[language] = asyncio.Semaphore(size) diff --git a/sandbox/executor_manager/services/execution.py b/sandbox/executor_manager/services/execution.py index c196ef622..1371ee95f 100644 --- a/sandbox/executor_manager/services/execution.py +++ b/sandbox/executor_manager/services/execution.py @@ -82,20 +82,40 @@ const fs = require('fs'); const path = require('path'); const args = JSON.parse(process.argv[2]); - const mainPath = path.join(__dirname, 'main.js'); +function isPromise(value) { + return Boolean(value && typeof value.then === 'function'); +} + if (fs.existsSync(mainPath)) { - const { main } = require(mainPath); + const mod = require(mainPath); + const main = typeof mod === 'function' ? mod : mod.main; + + if (typeof main !== 'function') { + console.error('Error: main is not a function'); + process.exit(1); + } if (typeof args === 'object' && args !== null) { - main(args).then(result => { - if (result !== null) { - console.log(result); + try { + const result = main(args); + if (isPromise(result)) { + result.then(output => { + if (output !== null) { + console.log(output); + } + }).catch(err => { + console.error('Error in async main function:', err); + }); + } else { + if (result !== null) { + console.log(result); + } } - }).catch(err => { - console.error('Error in main function:', err); - }); + } catch (err) { + console.error('Error when executing main:', err); + } } else { console.error('Error: args is not a valid object:', args); }