08a79229a5
leo21c 實部署發現兩個 CF 限制並修正:
1. CF 禁 runtime 從 bytes 編譯 wasm(WebAssembly.instantiate(bytes) 被 embedder 擋)
→ singlefile(base64) 內嵌不可用。改 wasmfile variant + `import wasm from './vendor/quickjs.wasm'`
(wrangler CompiledWasm rule 綁成預編 WebAssembly.Module),newVariant({wasmModule}) 注入。
sandbox 改 variant 注入制(setVariant):Worker 注 wrangler Module、Node 由 bytes 建 Module,
同一 production 路徑受測。vendor/quickjs.wasm 由 postinstall 自 node_modules 複製(gitignored)。
2. CF 凍結同步執行期 Date.now → wall-clock deadline 對純同步迴圈失效(撞 CF CPU 回 1102)。
改指令計數 interrupt(max_ticks,CF-safe),wall-clock 留 Node 保護。校準:cadence≈5000
指令/tick、真實 card 解析≈4 ticks、CF 門檻≈1000+ ticks → 預設 max_ticks=500。
有 body 的迴圈(含 100M 迴圈)皆乾淨回 TimeoutError;空體 while(true){} 仍由 CF CPU guard 容納。
Worker live: arcrun-code.leo21c.workers.dev(cypher-executor 以此 workers.dev 慣例位址呼叫)。
Node 單測 12/12 綠(wasmfile variant + 注入 Module,同 production 路徑)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o
62 lines
2.8 KiB
TypeScript
62 lines
2.8 KiB
TypeScript
/**
|
||
* arcrun `code` 零件 —— Worker host(可部署)
|
||
*
|
||
* POST / → { code, input?, limits? }
|
||
* → QuickJS-wasm 沙箱(./sandbox.mjs 的 runCode)
|
||
* → { success:true, data } | { success:false, error, error_type }
|
||
*
|
||
* 封裝=A(quickjs-emscripten wasmfile variant)。關鍵:CF Workers 禁止 runtime 從 bytes
|
||
* 編譯 wasm(WebAssembly.instantiate(bytes) 被 embedder 擋),故不能用 singlefile(base64) 內嵌。
|
||
* 改為 `import wasm from '.../wasm'` 讓 wrangler 在 build 時把 .wasm 綁成一個「已編好的
|
||
* WebAssembly.Module」,再以 newVariant({ wasmModule }) 注入沙箱 → 執行期只 instantiate 既有
|
||
* Module、不編譯,合 Workers 規則。無需 nodejs_compat(sandbox 用 TextEncoder 計 bytes)。
|
||
*
|
||
* 與其他 logic 零件不同:`code` 是自足 Worker(自帶 index.ts + sandbox.mjs + quickjs variant),
|
||
* 不走 component-worker-template 的 TinyGo-wasm bundling 流程。部署見 DEPLOY.md。
|
||
*/
|
||
|
||
import { Hono } from 'hono';
|
||
import { cors } from 'hono/cors';
|
||
import { newVariant } from 'quickjs-emscripten-core';
|
||
import baseVariant from '@jitl/quickjs-wasmfile-release-sync';
|
||
// wrangler 把相對路徑 .wasm import 綁成 WebAssembly.Module(build 時編好,執行期不重編)。
|
||
// wasm 從 quickjs-wasmfile-release-sync vendored 進 vendor/(見 DEPLOY.md「vendor 步驟」)。
|
||
import wasmModule from './vendor/quickjs.wasm';
|
||
// @ts-expect-error —— sandbox.mjs 為 runtime-agnostic JS 核心(Node 測試與 Worker 共用同一份)
|
||
import { runCode, setVariant } from './sandbox.mjs';
|
||
|
||
// 注入預編 Module(模組載入時一次)。之後 runCode 只 instantiate、不編譯。
|
||
setVariant(newVariant(baseVariant, { wasmModule: wasmModule as WebAssembly.Module }));
|
||
|
||
const app = new Hono();
|
||
app.use('*', cors());
|
||
|
||
app.get('/', (c) => c.json({ ok: true, component: 'code' }));
|
||
|
||
app.post('/', async (c) => {
|
||
let body: { code?: unknown; input?: unknown; limits?: Record<string, number> };
|
||
try {
|
||
body = await c.req.json();
|
||
} catch {
|
||
return c.json({ success: false, error: 'request body must be JSON', error_type: 'ContractError' }, 400);
|
||
}
|
||
|
||
if (typeof body.code !== 'string') {
|
||
return c.json({ success: false, error: 'code (string) is required', error_type: 'ContractError' }, 400);
|
||
}
|
||
|
||
try {
|
||
const result = await runCode(body.code, body.input, { limits: body.limits });
|
||
// sandbox 永遠回結構化 envelope;success=false 仍以 200 帶 error_type 回(零件語義層錯,非 HTTP 錯)
|
||
return c.json(result);
|
||
} catch (e) {
|
||
// 理論上 runCode 自己 try/catch;這層是最後保險,Worker 絕不掛。
|
||
return c.json(
|
||
{ success: false, error: e instanceof Error ? e.message : String(e), error_type: 'SandboxError' },
|
||
500,
|
||
);
|
||
}
|
||
});
|
||
|
||
export default app;
|