Summary
Studio serves intermittent HTTP 500 errors under concurrent request loads that occur during normal site use — a browser page load firing parallel requests for HTML, CSS, JS, REST API, images, etc. Users see blank pages or broken assets until they refresh.
The symptom is visible at ordinary concurrency levels (10–25 parallel requests — less than a typical WordPress admin page load). A fresh install with Twenty Twenty-Five theme reproduces it.
Reproduction
With any Studio site running:
# Warm up
curl -s -o /dev/null http://localhost:8881/
sleep 2
# 15 concurrent requests × 5 rounds
for r in 1 2 3 4 5; do
for i in $(seq 1 15); do
curl -s -o /dev/null -w "%{http_code} " "http://localhost:8881/?r=${r}i=${i}" &
done
wait
echo " <- round $r"
done
Observed on stock Studio (1.7.8)
500 500 500 200 200 200 200 200 200 200 200 200 200 200 200 <- round 1
500 500 500 500 500 500 200 200 200 200 200 200 200 200 200 <- round 2
500 500 500 500 500 200 200 200 200 200 200 200 200 200 200 <- round 3
...
First 3–7 requests of each concurrent burst fail. Body is literally Internal Server Error (21 bytes) from Playground's express error-catch path in start-server.ts.
Root Cause
The bug lives in @wp-playground/cli and @php-wasm/universal, not in Studio itself. Two overlapping issues:
1. Pool proxy releases workers before PHP finishes
handleRequest calls playgroundPool.requestStreamed() through the object pool proxy. The proxy releases the worker when requestStreamed() resolves — but that resolves when streaming starts, not when PHP finishes. The worker's SinglePHPInstanceManager is still acquired. A subsequent request routed to that "free" worker hits isAcquired === true and throws → 500.
2. Crashed workers never evicted from the pool
When a worker thread exits unexpectedly (e.g. EADDRINUSE during a port race), its API proxy stays in the pool. Requests routed to the dead proxy fail, release back, and the cycle continues. The pool permanently shrinks.
Upstream Fix
Both issues are fixed upstream (PR open, needs review/merge):
The fix switches handleRequest to buffered request() with a callback pattern so the full PHP lifecycle stays within the pool proxy's scope, and adds __removeInstance() to createObjectPoolProxy wired to the onExit handler so dead workers get evicted.
Verified fix
Built the Playground PR locally and installed it into Studio via the tarball self-hosting workflow, then re-ran the concurrency benchmark against the resulting dev build:
- 15 concurrent × 5 rounds: 75/75 pass
- 25 concurrent × 10 rounds: 250/250 pass
- Zero 500s
Impact on Studio
Every Studio user hits this whenever their browser fires concurrent requests during a page load — which is most of them. The symptom is often dismissed as "weird, refresh worked" but it degrades:
- Page load reliability (broken CSS/JS/images on first load)
- REST API calls from the block editor (autosave, block loading)
- External clients doing parallel requests (mobile apps, staging syncs, benchmarks)
- WP-CLI commands that internally parallelize
Action Needed
Once WordPress/wordpress-playground#3494 merges and a new @wp-playground/* version is published, Studio bumps the pinned version in apps/cli/package.json (currently 3.1.19) and rebuilds.
Filing here so:
- Studio users encountering the 500s have a public explanation.
- There's a Studio-side tracker for the dep bump when the Playground PR merges.
- Studio team has visibility into a user-facing bug whose fix is waiting on upstream review.
Environment
- Studio 1.7.8 on macOS
- Default Twenty Twenty-Five theme, no additional plugins
- Reproduced against every Studio site tested
Summary
Studio serves intermittent HTTP 500 errors under concurrent request loads that occur during normal site use — a browser page load firing parallel requests for HTML, CSS, JS, REST API, images, etc. Users see blank pages or broken assets until they refresh.
The symptom is visible at ordinary concurrency levels (10–25 parallel requests — less than a typical WordPress admin page load). A fresh install with Twenty Twenty-Five theme reproduces it.
Reproduction
With any Studio site running:
Observed on stock Studio (1.7.8)
First 3–7 requests of each concurrent burst fail. Body is literally
Internal Server Error(21 bytes) from Playground's express error-catch path instart-server.ts.Root Cause
The bug lives in
@wp-playground/cliand@php-wasm/universal, not in Studio itself. Two overlapping issues:1. Pool proxy releases workers before PHP finishes
handleRequestcallsplaygroundPool.requestStreamed()through the object pool proxy. The proxy releases the worker whenrequestStreamed()resolves — but that resolves when streaming starts, not when PHP finishes. The worker'sSinglePHPInstanceManageris still acquired. A subsequent request routed to that "free" worker hitsisAcquired === trueand throws → 500.2. Crashed workers never evicted from the pool
When a worker thread exits unexpectedly (e.g.
EADDRINUSEduring a port race), its API proxy stays in the pool. Requests routed to the dead proxy fail, release back, and the cycle continues. The pool permanently shrinks.Upstream Fix
Both issues are fixed upstream (PR open, needs review/merge):
The fix switches
handleRequestto bufferedrequest()with a callback pattern so the full PHP lifecycle stays within the pool proxy's scope, and adds__removeInstance()tocreateObjectPoolProxywired to theonExithandler so dead workers get evicted.Verified fix
Built the Playground PR locally and installed it into Studio via the tarball self-hosting workflow, then re-ran the concurrency benchmark against the resulting dev build:
Impact on Studio
Every Studio user hits this whenever their browser fires concurrent requests during a page load — which is most of them. The symptom is often dismissed as "weird, refresh worked" but it degrades:
Action Needed
Once WordPress/wordpress-playground#3494 merges and a new
@wp-playground/*version is published, Studio bumps the pinned version inapps/cli/package.json(currently3.1.19) and rebuilds.Filing here so:
Environment