Skip to content

Give each PHP worker a distinct port - #5259

Merged
chubes4 merged 4 commits into
trunkfrom
fix/php-worker-port-race
Oct 27, 2026
Merged

chubes4 merged 4 commits into
trunkfrom
fix/php-worker-port-race

Conversation

@chubes4

@chubes4 chubes4 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Related issues

  • CLI E2E on Linux failed twice in the last 200 builds (23113 and 23272) with Failed to listen on 127.0.0.1:44047 (reason: Address already in use), followed by PHP worker 3/4 exited unexpectedly and Failed to start WordPress server.

How AI was used in this PR

Claude (via the Pi coding agent in a Roadie session) found the failure in the CI logs, traced it to the worker port allocation, and wrote the change and the test. I reviewed the diff and the test results.

Proposed Changes

A native PHP site runs a pool of 4 php -S workers. php-server-child.ts picked their ports one at a time: bind a probe server to port 0, read the port, close the probe, then pick the next. Once a probe closes, its port is free again, so a later probe can be handed the same port. Linux picks ephemeral ports at random, so this happens rarely: about 1 in 1,000 site starts, measured on a Linux machine. When it does, two workers get the same port, the second fails to bind it, and the site does not start.

  • getAvailablePorts( count ) in a new lib/native-php/worker-ports.ts keeps every probe bound until all ports are picked, then releases them together. Ports picked this way cannot repeat.
  • php-server-child.ts uses it in place of the one-at-a-time loop.

Ports are still released before php -S binds them, so another process could in principle take one in that gap. That race is unchanged, and it is much rarer than one pool reusing its own port.

Testing Instructions

  • npx vitest run --project cli lib/native-php/tests/worker-ports.test.ts. The test fakes an OS that always hands out the lowest free port. Trunk's loop gets the same port 4 times there (expected 1 to be 4); the new function returns 4 distinct ports and releases them all.
  • Real kernel, outside the test: picking 4 ports the way trunk does repeated a port in 98 of 100,000 pools on Linux (0 of 2,000 on macOS). With this change a pool can't repeat a port.
  • npm run cli:build && npm test -- --project cli --tagsFilter=e2e --no-file-parallelism .e2e.test.ts: 42 passed locally.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

Worker ports were picked one at a time, each probe closed before the next,
so the OS could return a port it had just released. Two workers then got
the same port and one failed to bind it, stopping the site from starting.
Keep every probe bound until all ports are chosen.
@wpmobilebot

wpmobilebot commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing b3d8eec vs trunk

app-size

Metric trunk b3d8eec Diff Change
App Size (Mac) 954.28 MB 954.28 MB 0.00 MB ⚪ 0.0%

site-editor

Metric trunk b3d8eec Diff Change
load 1255 ms 1201 ms 54 ms 🟢 -4.3%

site-startup

Metric trunk b3d8eec Diff Change
siteCreation 5840 ms 5606 ms 234 ms 🟢 -4.0%
siteStartup 2858 ms 2888 ms +30 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@chubes4
chubes4 merged commit 5187811 into trunk Oct 27, 2026
12 checks passed
@chubes4
chubes4 deleted the fix/php-worker-port-race branch October 27, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants