Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
"test:playground-custom-archive-cache": "tsx tests/playground-custom-archive-cache.test.ts && tsx tests/playground-custom-archive-cache-process.test.ts && tsx tests/playground-custom-archive-cache.integration.test.ts",
"test:phpunit-runtime-failure-diagnostics": "tsx tests/phpunit-runtime-failure-diagnostics.test.ts",
"test:phpunit-runtime-rejection": "tsx tests/phpunit-runtime-rejection.test.ts",
"test:playground-worker-runtime-rejection": "tsx tests/playground-worker-runtime-rejection.test.ts",
"test:php-wasm-extension-manifests": "tsx tests/php-wasm-extension-manifests.test.ts",
"test:browser-task-builder": "tsx tests/browser-task-builder.test.ts",
"test:browser-runtime-generic-invoker": "tsx tests/browser-runtime-generic-invoker.test.ts",
Expand Down
26 changes: 26 additions & 0 deletions patches/@wp-playground+cli+3.1.46.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
diff --git a/node_modules/@wp-playground/cli/worker-thread-v1.js b/node_modules/@wp-playground/cli/worker-thread-v1.js
index 93b7099..65f76be 100644
--- a/node_modules/@wp-playground/cli/worker-thread-v1.js
+++ b/node_modules/@wp-playground/cli/worker-thread-v1.js
@@ -157,6 +157,8 @@ async function C(r, e) {
}
process.on("unhandledRejection", (r) => {
S.error("Unhandled rejection:", r);
+ if (r instanceof WebAssembly.RuntimeError && String(r.stack).includes("php.wasm"))
+ throw r;
});
const i = new _(), [c, p] = y(
new F(new f()),
diff --git a/node_modules/@wp-playground/cli/worker-thread-v2.js b/node_modules/@wp-playground/cli/worker-thread-v2.js
index fb79a59..77ddb67 100644
--- a/node_modules/@wp-playground/cli/worker-thread-v2.js
+++ b/node_modules/@wp-playground/cli/worker-thread-v2.js
@@ -168,6 +168,8 @@ async function j(r, e) {
}
process.on("unhandledRejection", (r) => {
E.error("Unhandled rejection:", r);
+ if (r instanceof WebAssembly.RuntimeError && String(r.stack).includes("php.wasm"))
+ throw r;
});
const i = new m(), [h, u] = k(
new L(new b()),
7 changes: 7 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ WP Codebox needs the repaired bounded Range decoder for the Cloudflare dev rig
and browser asset path before that upstream change can be merged and released.
Remove this patch and the package override after a published Playground package
contains the same change.

`@wp-playground+cli+3.1.46.patch` makes blueprint worker threads rethrow fatal
`WebAssembly.RuntimeError` rejections originating from `php.wasm` after logging
them. Other unhandled rejections retain the released CLI's log-only behavior.
Without this narrow terminalization, a poisoned PHP-WASM worker remains alive
and leaves the parent request pending until the recipe timeout. Remove the patch
after the Playground CLI ships the same worker terminalization behavior.
73 changes: 73 additions & 0 deletions tests/playground-worker-runtime-rejection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import assert from "node:assert/strict"
import { mkdtemp, rm, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join, resolve } from "node:path"
import { pathToFileURL } from "node:url"
import { Worker } from "node:worker_threads"

const root = await mkdtemp(join(tmpdir(), "wp-codebox-playground-worker-rejection-"))
const preloadPath = join(root, "reject-on-command.mjs")

await writeFile(preloadPath, `
import { parentPort } from "node:worker_threads"
parentPort?.on("message", (message) => {
if (message === "wp-codebox-trigger-non-wasm-rejection") {
Promise.reject(new Error("ordinary worker rejection"))
setTimeout(() => parentPort?.postMessage("wp-codebox-worker-survived"), 25)
}
if (message === "wp-codebox-trigger-php-wasm-rejection") {
const error = new WebAssembly.RuntimeError("null function or function signature mismatch")
error.stack = "RuntimeError: null function or function signature mismatch\\n at php.wasm.zif_mysqli_poll (wasm://wasm/php.wasm-05996276:wasm-function[12986]:0x9949a8)"
Promise.reject(error)
}
})
`, "utf8")

try {
for (const version of ["v1", "v2"]) {
await assertWorkerTerminates(version)
}
} finally {
await rm(root, { recursive: true, force: true })
}

async function assertWorkerTerminates(version: string): Promise<void> {
const workerPath = resolve(`node_modules/@wp-playground/cli/worker-thread-${version}.js`)
const worker = new Worker(pathToFileURL(workerPath), {
execArgv: ["--import", pathToFileURL(preloadPath).href],
})

let workerError: Error | undefined
let initialized = false
let survivedOrdinaryRejection = false
const exitCode = await Promise.race([
new Promise<number>((resolveExit) => {
worker.once("message", (message: { command?: unknown; phpPort?: { close?: () => void } }) => {
if (message?.command !== "worker-script-initialized") return
initialized = true
message.phpPort?.close?.()
worker.postMessage("wp-codebox-trigger-non-wasm-rejection")
})
worker.on("message", (message: unknown) => {
if (message !== "wp-codebox-worker-survived") return
survivedOrdinaryRejection = true
worker.postMessage("wp-codebox-trigger-php-wasm-rejection")
})
worker.once("error", (error) => {
workerError = error
})
worker.once("exit", resolveExit)
}),
new Promise<never>((_resolve, reject) => {
setTimeout(() => reject(new Error(`Playground ${version} worker remained alive after a fatal PHP-WASM rejection`)), 3_000).unref()
}),
]).finally(() => worker.terminate())

assert.ok(initialized, `Playground ${version} worker must initialize before the rejection checks`)
assert.ok(survivedOrdinaryRejection, `Playground ${version} worker must preserve ordinary unhandled rejection behavior`)
assert.notEqual(exitCode, 0, `Playground ${version} worker must exit nonzero after a fatal PHP-WASM rejection`)
assert.ok(workerError, `Playground ${version} worker must propagate its fatal PHP-WASM rejection`)
assert.match(workerError.message, /null function or function signature mismatch/)
}

console.log("playground PHP-WASM worker rejection terminalization ok")
Loading