Description
We recently discovered that some of our code, was producing identical random numbers every single run.
We ended up tracing it to a 3rd party library from packagist which started using MT rand when it hadn't before.
Prior to that update, the first use of MT rand occurred after the pcntl_fork(), so we got a unique MT seed in each child process.
After the update, despite no changes near this code, we suddenly saw the exact same output from array_rand() every single time. A CSPRNG would not be affected by this.
I've mitigated this in my codebase, by ensuring we always call mt_srand() in the child immediately after every pcntl_fork() call. But the fact that it could work for years then suddenly change because the MT rand was initialized prior to the fork was surprising behavior.
I would like to propose that pcntl_fork() should automatically reseed mt_rand() (if it has already been seeded) to help ensure consistent behavior regardless of whether MT rand was used prior to forking or not.
<?php
declare(strict_types=1);
// MT rand is used under the hood by a number of functions like array_rand()
// if it's not used prior to pcntl_fork() (eg. by commenting the next line) then the behavior changes
mt_rand();
for ($i = 0; $i < 10; $i++) {
$PID = pcntl_fork();
if ($PID == 0) {
// It's important to always reiniatilize the MT seed after forking
// MT rand seed is lazy initialized and that leads to inconsistent behavior:
// a. If MT rand is first used after the fork, it works correctly
// b. If MT rand is first used before the fork, then the child will inherit the seed
// and produce the exact same "random" number sequence every single run
// We learned this after it worked for years, then an innocuous change in a composer
// library call prior to the fork, suddenly caused the same predictable sequence every time
// mt_srand()
echo "child $i: " . array_rand(["a","b","c","d","e","f","g","h"]) . "\n";
exit;
} else {
pcntl_wait($Status);
}
}
Resulted in this output:
child 0: 7
child 1: 7
child 2: 7
child 3: 7
child 4: 7
child 5: 7
child 6: 7
child 7: 7
child 8: 7
child 9: 7
But I expected this output instead:
child 0: 4
child 1: 7
child 2: 1
child 3: 4
child 4: 3
child 5: 3
child 6: 0
child 7: 3
child 8: 7
child 9: 1
PHP Version
PHP 8.5.3 (cli) (built: Mar 5 2026 20:29:09) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.3, Copyright (c) Zend Technologies
with Zend OPcache v8.5.3, Copyright (c), by Zend Technologies
Operating System
Gentoo Linux
Description
We recently discovered that some of our code, was producing identical random numbers every single run.
We ended up tracing it to a 3rd party library from packagist which started using MT rand when it hadn't before.
Prior to that update, the first use of MT rand occurred after the pcntl_fork(), so we got a unique MT seed in each child process.
After the update, despite no changes near this code, we suddenly saw the exact same output from array_rand() every single time. A CSPRNG would not be affected by this.
I've mitigated this in my codebase, by ensuring we always call mt_srand() in the child immediately after every pcntl_fork() call. But the fact that it could work for years then suddenly change because the MT rand was initialized prior to the fork was surprising behavior.
I would like to propose that pcntl_fork() should automatically reseed mt_rand() (if it has already been seeded) to help ensure consistent behavior regardless of whether MT rand was used prior to forking or not.
Resulted in this output:
But I expected this output instead:
PHP Version
Operating System
Gentoo Linux