I understand how fork()
works at a high level and the pcntl_fork()
wrapper, but for some reason in my environment PHP only runs two child processes at a time. Take for example this simple code:
<?php
for ($i = 1; $i <= 5; ++$i) {
$pid = pcntl_fork();
if ($pid === -1) {
print "Could not fork!n";
exit(1);
}
if (!$pid) {
print "-In child $in";
sleep(1);
print "In child $in";
exit($i);
} else {
print "Parent: forked $in";
}
}
while (pcntl_waitpid(0, $status) != -1) {
$status = pcntl_wexitstatus($status);
echo "Child $status completedn";
}
The output I expect is something like this with total time around 1 second:
Parent: forked 1
-In child 1
Parent: forked 2
-In child 2
Parent: forked 3
-In child 3
Parent: forked 4
-In child 4
Parent: forked 5
-In child 5
In child 1
In child 2
In child 3
In child 4
In child 5
Child 1 completed
Child 2 completed
Child 3 completed
Child 4 completed
Child 5 completed
But what I actually get is this, with total execution time around 3.5 seconds:
Parent: forked 1
Parent: forked 2
Parent: forked 3
Parent: forked 4
Parent: forked 5
-In child 2
-In child 1
In child 2
Child 2 completed
-In child 3
In child 1
Child 1 completed
-In child 4
In child 3
Child 3 completed
-In child 5
In child 4
Child 4 completed
In child 5
Child 5 completed
So it appears that only two child processes are actually running at any given time. I can’t find any explanation for this behavior…
When running the test on a production system which is Docker on a native Linux host I get the expected result, but why can’t I reproduce it with the exact same container on my WSL2 host?
System Information
- I’m running this test in a very recent version of Docker Desktop via WSL2 on Windows 11.
- My system has 12 CPU cores (20 with hyperthreading –
nproc
prints "20" from both the WSL2 hostand from inside the docker container). - I have not changed the defaults in Docker Desktop or WSL config or used any resource control flags on the Docker container so CPU should not be restricted in any way.
2
Answers
So after running the code with the expected result on a simplified Docker image with only PHP and pcntl installed, I discovered the behavior is related to XDebug.
I had PhpStorm open and listening for debug connections (with no breakpoints set) so I believe what was happening was each child process was connecting to the debugger and the debugger can only handle two connections at a time. Either that or it has something to do with the connection established by the parent process being closed in a child process.
So if you are using pcntl and XDebug together, expect weird results.. Turning off the debugger in my IDE or disabling XDebug via PHP CLI yields the expected results.
Running this on a native Linux box (i3-6100)….
I would have been surprised if it behaved exactly as you predicted. When a process forks, it creates 2 runnable processes….as to which gets scheduled first….with the CFS they are likely to stay associated with the same core….but it will still be effectively random. And PHP does have a lot of work to do (including stuff which will result in yielding the CPU) when it starts – even though the fork should mean that most of this can be shortcutted. Then there is the sequence in which buffers get flushed to the tty.
Having said all that, your results are a very long way from your expectation. This appears to be a result of the MS-Windows scheduler (which IME is very lumpy).