A long-running child process can print its readiness line before waitUntil() begins—or another status check can consume that chunk. On Windows, poll the accumulated output as the source of truth and enforce a startup deadline.
Last updated: November 3, 2026.
<?php
$process->setTimeout(null);
$process->start();
$deadline = microtime(true) + 10;
while ($process->isRunning()) {
if (str_contains($process->getOutput(), 'Listening on')) {
break;
}
if (microtime(true) >= $deadline) {
$process->stop(1);
throw new RuntimeException('Server did not become ready');
}
usleep(50_000);
}This checks everything captured so far, not only the next output chunk. Use a marker that is unique, stable, and written only after the child can accept work.
Choose the waiting model deliberately
Symfony’s Process documentation distinguishes asynchronous start(), blocking wait(), and waitUntil(), whose callback inspects new output. A callback is convenient when the marker is guaranteed to arrive after waiting starts. Accumulated-output polling is safer when startup and waiting can race.
Do not use an unlimited loop. A process timeout limits total runtime, while a readiness deadline limits only startup. Decide whether the child should be terminated on startup failure and collect both standard output and error output before throwing an exception.
Make intermittent failures reproducible
Run the startup sequence hundreds of times on the affected Windows version and record the Symfony, PHP, and child-program versions. Add timestamps immediately before start(), each status check, readiness detection, and termination. The failure is a timing problem, so a single successful manual run is weak evidence.
Prefer the array form of Process arguments instead of a shell command when shell parsing is unnecessary. Ensure the child flushes its readiness line; buffered output can look like a waiting bug. If you upgrade Symfony after an upstream fix, keep the deadline because external programs can still stall for unrelated reasons.
After detecting readiness, continue managing the long-running child: capture its PID, handle shutdown, and verify that failed tests do not leave orphaned processes. Continue with PHP execution limits, stream timeout detection, and PHP exceptions and Throwable.