接续L4-TOOL-DB浏览器Provider协议

结论:浏览器 Worker 已支持可插拔 Python runtime provider 的 line I/O,缺真实 Python/WASM runtime 时仍保持阻塞不解锁。
This commit is contained in:
2026-06-19 07:13:52 +08:00
parent 63870fd6d7
commit db33224dd0
6 changed files with 159 additions and 4 deletions

View File

@@ -577,7 +577,9 @@ private module paths:
boundary. The bundled worker reports boundary. The bundled worker reports
`blocked_browser_python_wasm_runtime_missing` until a real Python/WASM runtime `blocked_browser_python_wasm_runtime_missing` until a real Python/WASM runtime
is installed; the Worker shell alone must not mark `DB_PROGRAM` execution 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 - `createLinuxCncToolDbNodeRuntimeAdapter()` in
`runtime/sdk/src/tool-db-node-runtime-adapter.js` for Node/native verification `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 of the real LinuxCNC `DB_PROGRAM` child process. This adapter is intentionally

View File

@@ -96,7 +96,14 @@ export function createLinuxCncToolDbBrowserWorkerAdapter({
return result.line; 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.")); rejectPending(new Error("Tool DB browser worker closed."));
worker?.terminate(); worker?.terminate();
worker = null; worker = null;

View File

@@ -15,12 +15,21 @@ function pythonRuntimeAvailable() {
return typeof self.loadPyodide === "function" || Boolean(self.pyodide?.runPythonAsync); return typeof self.loadPyodide === "function" || Boolean(self.pyodide?.runPythonAsync);
} }
function getRuntimeProvider() {
return self.linuxCncToolDbPythonRuntimeProvider ?? null;
}
self.addEventListener("message", async (event) => { self.addEventListener("message", async (event) => {
const message = event.data ?? {}; const message = event.data ?? {};
const { id, type } = message; const { id, type, line } = message;
try { try {
if (type === "start") { 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, { postReply(id, type, {
runtimeMode: "browser-python-wasm-worker", runtimeMode: "browser-python-wasm-worker",
runtimeExecutionReady: ready, runtimeExecutionReady: ready,
@@ -32,6 +41,25 @@ self.addEventListener("message", async (event) => {
}); });
return; 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"); throw new Error("blocked_browser_python_wasm_runtime_missing");
} catch (error) { } catch (error) {
postError(id, type, error); postError(id, type, error);

View File

@@ -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");

View File

@@ -85,6 +85,23 @@
assertEqual(workerPlanResult.promotionAllowed, false, "worker promotion guard"); assertEqual(workerPlanResult.promotionAllowed, false, "worker promotion guard");
await workerPort.close(); 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 = [ const transcript = [
{ direction: "read", line: "v2.1" }, { direction: "read", line: "v2.1" },
{ direction: "write", line: "g" }, { direction: "write", line: "g" },

View File

@@ -80,6 +80,66 @@ class FakeBlockedWorker {
terminate() {} 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({ const browserWorkerAdapter = createLinuxCncToolDbBrowserWorkerAdapter({
workerUrl: "./tool-db-python-worker.js", workerUrl: "./tool-db-python-worker.js",
workerFactory: () => new FakeBlockedWorker(), workerFactory: () => new FakeBlockedWorker(),
@@ -99,6 +159,25 @@ assert.equal(browserWorkerBlockedPlan.runtimeExecutionReady, false);
assert.equal(browserWorkerBlockedPlan.promotionAllowed, false); assert.equal(browserWorkerBlockedPlan.promotionAllowed, false);
await browserWorkerPort.close(); 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 = [ const transcript = [
{ direction: "read", line: "v2.1" }, { direction: "read", line: "v2.1" },
{ direction: "write", line: "g" }, { direction: "write", line: "g" },