PHP 5.3.4 Changes rand(), Filled My Error Log, Spikes Load

I ran into a peculiar situation with a PHP web application that went from working for several years without incident to suddenly resulting in timeouts and spiking the load on my server. Some investigation traced it back to a seemingly benign and obscure change to PHP’s rand() implementation between 5.3.3 and 5.3.4.

To summarize several hundred lines of code: it gets a value from an array where the index is a random number between X and Y. X and Y are highly unpredictable by nature of the application. It keeps trying with different values until something is returned. Something like:

function random($a, $b){
   if(MT_RAND){
       return mt_rand($a, $b);
   }
   return rand($a, $b);
}
 
$x = 0;
while($x == 0){
    $x = $arr[random($x, $y)];
}
return $x;

See it? If you don’t, you shouldn’t feel bad, I didn’t see it initially either.

Prior to PHP 5.3.4 mt_/rand did not check if the max is greater than the min. This has changed as a result of bug 46587. That 4 line change made an impact.

Take this example code:

print "right: " . mt_rand(1, 5) . "\n";
print "wrong: " . mt_rand(5, 1) . "\n";

In PHP 5.3.3 you’d get:

$ php test.php
right: 3
wrong: 4

$ php test.php
right: 2
wrong: 5

Despite the incorrect order of max/min it actually worked just fine. It had done so at least since PHP 4.3 (circa 2003) as far as I’m aware.

In PHP 5.3.4:

$ php test.php
right: 2
PHP Warning:  mt_rand(): max(1) is smaller than min(5) in /test/test.php on line 4
wrong: 

As a result, this while(){} never terminated until the timeout was reached.

The solution is obviously trivial once you actually trace this bug back:

function random($a, $b){
   
    // If a is greater, flip order
    if($a > $b){
        $tmp = $a;
        $a = $b;
        $b = $tmp;
   }
       
   if(MT_RAND){
       return mt_rand($a, $b);
   }
   return rand($a, $b);
}

This resulted in several GB’s worth of warnings in my error log in a matter of hours. You can also see how it (the brown area) dropped off once the fix was deployed as measured by % of wall clock time:

Transaction %

It’s the little things sometimes that cause all the trouble.

One thought on “PHP 5.3.4 Changes rand(), Filled My Error Log, Spikes Load

  1. Hi,

    How can I fix the mt_rand and have a flip it code on this code layout:

    function AddVertLines($im_image,$i_width,$i_height,$s_x_func,$i_color,
    $a_text_area,$i_char_width,$i_spacing)
    {
    global $bCleanText;

    $i_text_left = $a_text_area[0];
    $i_text_top = $a_text_area[1];
    $i_text_right = $a_text_area[2];
    $i_text_bot = $a_text_area[3];
    for (eval(‘$ii = ‘.$s_x_func.’;’) ; $ii = $i_text_left && $ii < $i_text_right)
    {
    $i_char_pos = $ii – $i_text_left;
    if (((int) ($i_char_pos / $i_char_width)) % $i_spacing == 0)
    $b_split = TRUE;
    else
    {

    $i_start = mt_rand(0,$i_text_top);
    $i_end = mt_rand($i_text_bot,$i_height-1);
    }
    }
    if ($b_split)
    {

    imageline($im_image,$ii,min($i_text_top-2,$i_start),$ii,$i_text_top,$i_color);
    imageline($im_image,$ii,$i_text_bot+2,$ii,max($i_text_bot+2,$i_end),$i_color);
    }
    else
    imageline($im_image,$ii,$i_start,$ii,$i_end,$i_color);
    }
    }

    function AddNoise($im_image,$i_width,$i_height,$i_color,$a_text_area)
    {
    global $bCleanText;

    $i_text_left = $a_text_area[0] – 4;
    $i_text_top = $a_text_area[1];
    $i_text_right = $a_text_area[2];
    $i_text_bot = $a_text_area[3] + 4;
    $n_pixels = mt_rand(($i_width * $i_height) / 20,($i_width * $i_height) / 10);
    for ($ii = 0 ; $ii = $i_text_left && $i_x_pos = $i_text_top && $i_y_pos <= $i_text_bot)
    if (mt_rand(0,2) != 1)
    continue;
    imagesetpixel($im_image,$i_x_pos,$i_y_pos,$i_color);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *