Skip to content

[CLI] Avoid port conflicts when starting PHP-WASM network proxies - #3722

Merged
brandonpayton merged 7 commits into
WordPress:trunkfrom
chubes4:fix/cli-port-zero-eaddrinuse-retry
Jul 2, 2026
Merged

[CLI] Avoid port conflicts when starting PHP-WASM network proxies#3722
brandonpayton merged 7 commits into
WordPress:trunkfrom
chubes4:fix/cli-port-zero-eaddrinuse-retry

Conversation

@chubes4

@chubes4 chubes4 commented May 31, 2026

Copy link
Copy Markdown
Contributor

What?

Binds php-wasm networking proxy servers directly on automatic ports (0) and reads back assigned ports, instead of probing free ports first and rebinding concrete ports later.

Why?

Fixes #3721.

The deeper root cause is in @php-wasm/node networking, not the top-level Playground CLI HTTP server:

  • withNetworking() previously called findFreePorts(2).
  • findFreePorts() opened temporary net.Server instances on 0, read their assigned ports, then closed them.
  • The outbound websocket proxy later attempted to bind a new HTTP/WebSocket server to one of those concrete ports on 127.0.0.1.
  • The inbound websocket proxy used the same preselected concrete port pattern through addTCPServerToWebSocketServerClass().
  • Those probe-then-bind gaps are inherently racy. On a host with many local listeners, another process can already own or claim the selected concrete port by the time a proxy binds it.

Tracing a downstream WP Codebox smoke test showed the relevant outbound sequence:

[listen:http] args=0
    at app.listen (.../express/lib/application.js:635:24)
    at startServer (.../packages/playground/cli/src/start-server.ts:46:5)
[listen:http:listening] address={"address":"::","family":"IPv6","port":55974}

[listen:net] args=0
    at listenOnRandomPort (.../node_modules/@php-wasm/node/index.js:93:12)
    at findFreePorts (.../node_modules/@php-wasm/node/index.js:79:26)
    at withNetworking (.../node_modules/@php-wasm/node/index.js:365:71)

[listen:http] args=55976, "127.0.0.1"
    at initOutboundWebsocketProxyServer (.../node_modules/@php-wasm/node/index.js:162:15)
    at withNetworking (.../node_modules/@php-wasm/node/index.js:366:44)

So the CLI was already passing 0 correctly for its main server. The collision came from php-wasm choosing concrete “free” proxy ports before actually starting those proxies.

How?

  • Starts the outbound websocket proxy with listenPort = 0.
  • Reads the assigned outbound websocket proxy port from the started server and uses that port in generated websocket URLs.
  • Starts the inbound websocket proxy with options.port = 0 inside the server decorator.
  • Reads the assigned inbound websocket proxy port after the websocket server is listening and uses that port for the TCP bridge.
  • Defers the inbound ready callback until both the websocket server and TCP bridge are listening, while preserving the callback this binding.
  • Closes the inbound TCP bridge when the decorated websocket server closes.
  • Removes the obsolete findFreePorts() helper and centralizes assigned-port lookup in getServerPort().
  • Adds error rejection to outbound proxy startup so bind failures propagate.
  • Adds regression coverage verifying both inbound and outbound websocket proxies bind directly to automatic ports.

Testing

  • npx nx typecheck php-wasm-node
  • npx vitest run packages/php-wasm/node/src/test/networking-port-allocation.spec.ts --config packages/php-wasm/node/vite.config.ts

AI assistance

  • AI assistance: Yes
  • Tool(s): OpenCode (GPT-5.5)
  • Used for: Traced the downstream failure, identified the php-wasm networking TOCTOU, drafted the focused fix and regression tests, did cleanup/review passes, and ran targeted verification; Chris remains responsible for review and merge.
@chubes4 chubes4 changed the title Fix automatic CLI port binding collisions May 31, 2026

@brandonpayton brandonpayton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix, @chubes4! It makes sense, reads well, and tests well. Let's merge.

@brandonpayton
brandonpayton merged commit 9388b6d into WordPress:trunk Jul 2, 2026
102 of 103 checks passed
@brandonpayton brandonpayton changed the title Fix outbound proxy automatic port allocation Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment