From db33224dd0ee948f76e6122b846e306695211f10 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Fri, 19 Jun 2026 07:13:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E7=BB=ADL4-TOOL-DB=E6=B5=8F=E8=A7=88?= =?UTF-8?q?=E5=99=A8Provider=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:浏览器 Worker 已支持可插拔 Python runtime provider 的 line I/O,缺真实 Python/WASM runtime 时仍保持阻塞不解锁。 --- wasm-port/runtime/sdk/README.md | 4 +- .../sdk/src/tool-db-browser-worker-adapter.js | 9 ++- .../runtime/workers/tool-db-python-worker.js | 32 +++++++- .../tool_db_fake_python_runtime_worker.js | 22 ++++++ .../tool_db_process_browser_smoke.html | 17 ++++ .../sdk/node/verify_tool_db_process_port.mjs | 79 +++++++++++++++++++ 6 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 wasm-port/tests/browser/tool_db_fake_python_runtime_worker.js diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 6d3fc9e..9ed83f4 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -577,7 +577,9 @@ private module paths: boundary. The bundled worker reports `blocked_browser_python_wasm_runtime_missing` until a real Python/WASM runtime is installed; the Worker shell alone must not mark `DB_PROGRAM` execution - ready. + ready. The Worker supports a provider protocol (`start`, `writeLine`, + `readLine`, `close`) so a future Python/WASM runtime can provide line-based + `DB_PROGRAM` I/O without changing `ToolDbProcessPort`. - `createLinuxCncToolDbNodeRuntimeAdapter()` in `runtime/sdk/src/tool-db-node-runtime-adapter.js` for Node/native verification of the real LinuxCNC `DB_PROGRAM` child process. This adapter is intentionally diff --git a/wasm-port/runtime/sdk/src/tool-db-browser-worker-adapter.js b/wasm-port/runtime/sdk/src/tool-db-browser-worker-adapter.js index f0bebd0..3c023c1 100644 --- a/wasm-port/runtime/sdk/src/tool-db-browser-worker-adapter.js +++ b/wasm-port/runtime/sdk/src/tool-db-browser-worker-adapter.js @@ -96,7 +96,14 @@ export function createLinuxCncToolDbBrowserWorkerAdapter({ return result.line; }, - close() { + async close() { + if (worker) { + try { + await request("close"); + } catch { + // The worker may be blocked or already gone; termination below is authoritative. + } + } rejectPending(new Error("Tool DB browser worker closed.")); worker?.terminate(); worker = null; diff --git a/wasm-port/runtime/workers/tool-db-python-worker.js b/wasm-port/runtime/workers/tool-db-python-worker.js index 5e12229..8fb32c1 100644 --- a/wasm-port/runtime/workers/tool-db-python-worker.js +++ b/wasm-port/runtime/workers/tool-db-python-worker.js @@ -15,12 +15,21 @@ function pythonRuntimeAvailable() { return typeof self.loadPyodide === "function" || Boolean(self.pyodide?.runPythonAsync); } +function getRuntimeProvider() { + return self.linuxCncToolDbPythonRuntimeProvider ?? null; +} + self.addEventListener("message", async (event) => { const message = event.data ?? {}; - const { id, type } = message; + const { id, type, line } = message; try { if (type === "start") { - const ready = pythonRuntimeAvailable(); + const provider = getRuntimeProvider(); + const providerReady = Boolean(provider?.start && provider?.writeLine && provider?.readLine); + const ready = providerReady || pythonRuntimeAvailable(); + if (providerReady) { + await provider.start(message); + } postReply(id, type, { runtimeMode: "browser-python-wasm-worker", runtimeExecutionReady: ready, @@ -32,6 +41,25 @@ self.addEventListener("message", async (event) => { }); return; } + const provider = getRuntimeProvider(); + if (type === "writeLine" && provider?.writeLine) { + await provider.writeLine(line); + postReply(id, type, { executionEnabled: false, promotionAllowed: false }); + return; + } + if (type === "readLine" && provider?.readLine) { + postReply(id, type, { + line: await provider.readLine(), + executionEnabled: false, + promotionAllowed: false, + }); + return; + } + if (type === "close" && provider?.close) { + await provider.close(); + postReply(id, type, { executionEnabled: false, promotionAllowed: false }); + return; + } throw new Error("blocked_browser_python_wasm_runtime_missing"); } catch (error) { postError(id, type, error); diff --git a/wasm-port/tests/browser/tool_db_fake_python_runtime_worker.js b/wasm-port/tests/browser/tool_db_fake_python_runtime_worker.js new file mode 100644 index 0000000..e902b3b --- /dev/null +++ b/wasm-port/tests/browser/tool_db_fake_python_runtime_worker.js @@ -0,0 +1,22 @@ +self.linuxCncToolDbPythonRuntimeProvider = { + lines: [ + "v2.1", + "T10 P110 D0.10 Z0.10", + "FINI (get_cmd)", + "FINI (update recvd) t11 p111 d0.33 z0.11", + "FINI (update recvd) t14 p0", + "FINI (update recvd) t0 p0", + ], + + async start() {}, + + async writeLine() {}, + + async readLine() { + return this.lines.shift(); + }, + + async close() {}, +}; + +await import("../../runtime/workers/tool-db-python-worker.js"); diff --git a/wasm-port/tests/browser/tool_db_process_browser_smoke.html b/wasm-port/tests/browser/tool_db_process_browser_smoke.html index 11150d3..77b896e 100644 --- a/wasm-port/tests/browser/tool_db_process_browser_smoke.html +++ b/wasm-port/tests/browser/tool_db_process_browser_smoke.html @@ -85,6 +85,23 @@ assertEqual(workerPlanResult.promotionAllowed, false, "worker promotion guard"); await workerPort.close(); + const fakeRuntimeAdapter = createLinuxCncToolDbBrowserWorkerAdapter({ + workerUrl: new URL("./tool_db_fake_python_runtime_worker.js", import.meta.url), + }); + const fakeRuntimePort = createLinuxCncToolDbProcessPort({ + dbProgramPath: "./db_nonran.py", + runtimeMode: fakeRuntimeAdapter.runtimeMode, + runtimeAdapter: fakeRuntimeAdapter, + }); + const fakeRuntimeStart = await fakeRuntimePort.start(); + assertEqual(fakeRuntimeStart.runtimeExecutionReady, true, "fake provider runtime readiness"); + assertEqual(fakeRuntimeStart.status, "browser_python_wasm_runtime_ready", "fake provider runtime status"); + const fakeRuntimePlan = await fakeRuntimePort.runTransactionPlan(); + assertEqual(fakeRuntimePlan.status, "runtime_adapter_transaction_plan_executed", "fake provider plan"); + assertEqual(fakeRuntimePlan.promotionAllowed, false, "fake provider promotion guard"); + assertEqual(validateToolDbTranscript(fakeRuntimePort.exportTranscript()).ready, true, "fake provider transcript"); + await fakeRuntimePort.close(); + const transcript = [ { direction: "read", line: "v2.1" }, { direction: "write", line: "g" }, diff --git a/wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs b/wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs index 597f3a2..c5a5902 100644 --- a/wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs +++ b/wasm-port/tests/sdk/node/verify_tool_db_process_port.mjs @@ -80,6 +80,66 @@ class FakeBlockedWorker { terminate() {} } +class FakeReadyWorker { + constructor() { + this.listeners = new Map(); + this.lines = [ + "v2.1", + "T10 P110 D0.10 Z0.10", + "FINI (get_cmd)", + "FINI (update recvd) t11 p111 d0.33 z0.11", + "FINI (update recvd) t14 p0", + "FINI (update recvd) t0 p0", + ]; + } + + addEventListener(type, listener) { + this.listeners.set(type, listener); + } + + postMessage(message) { + queueMicrotask(() => { + if (message.type === "start") { + this.reply(message, { + runtimeExecutionReady: true, + status: "browser_python_wasm_runtime_ready", + executionEnabled: false, + promotionAllowed: false, + }); + return; + } + if (message.type === "writeLine") { + this.reply(message, { executionEnabled: false, promotionAllowed: false }); + return; + } + if (message.type === "readLine") { + this.reply(message, { + line: this.lines.shift(), + executionEnabled: false, + promotionAllowed: false, + }); + return; + } + if (message.type === "close") { + this.reply(message, { executionEnabled: false, promotionAllowed: false }); + } + }); + } + + reply(message, payload) { + this.listeners.get("message")?.({ + data: { + id: message.id, + type: message.type, + ok: true, + ...payload, + }, + }); + } + + terminate() {} +} + const browserWorkerAdapter = createLinuxCncToolDbBrowserWorkerAdapter({ workerUrl: "./tool-db-python-worker.js", workerFactory: () => new FakeBlockedWorker(), @@ -99,6 +159,25 @@ assert.equal(browserWorkerBlockedPlan.runtimeExecutionReady, false); assert.equal(browserWorkerBlockedPlan.promotionAllowed, false); await browserWorkerPort.close(); +const readyWorkerAdapter = createLinuxCncToolDbBrowserWorkerAdapter({ + workerUrl: "./tool-db-python-worker.js", + workerFactory: () => new FakeReadyWorker(), +}); +const readyWorkerPort = createLinuxCncToolDbProcessPort({ + dbProgramPath: "./db_nonran.py", + runtimeMode: readyWorkerAdapter.runtimeMode, + runtimeAdapter: readyWorkerAdapter, +}); +const readyWorkerStart = await readyWorkerPort.start(); +assert.equal(readyWorkerStart.runtimeExecutionReady, true); +assert.equal(readyWorkerStart.status, "browser_python_wasm_runtime_ready"); +const readyWorkerPlan = await readyWorkerPort.runTransactionPlan(); +assert.equal(readyWorkerPlan.status, "runtime_adapter_transaction_plan_executed"); +assert.equal(readyWorkerPlan.runtimeExecutionReady, true); +assert.equal(readyWorkerPlan.promotionAllowed, false); +assert.equal(validateToolDbTranscript(readyWorkerPort.exportTranscript()).ready, true); +await readyWorkerPort.close(); + const transcript = [ { direction: "read", line: "v2.1" }, { direction: "write", line: "g" },