php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #47712 (mysqlnd) zval cache on windows fails at high concurrency
Submitted: 2009-03-19 00:02 UTC Modified: 2010-01-25 18:02 UTC
Votes:10
Avg. Score:5.0 ± 0.0
Reproduced:10 of 10 (100.0%)
Same Version:10 (100.0%)
Same OS:10 (100.0%)
From: ninzya at inbox dot lv Assigned: mysql (profile)
Status: Closed Package: MySQL related
PHP Version: 5.3.0RC2 OS: win32 only
Private report: No CVE-ID: None
 [2009-03-19 00:02 UTC] ninzya at inbox dot lv
Description:
------------
My configuration is Windows XP, Apache 2.2.11.0, PHP 5.3.0beta1, i'm coding a framework and periodically i am performing stress tests with apache's ab.exe. Once my framework has become quite processive (lotsa operations involved in request processing), there started to appear unexpected PHP errors/warning in a PHP error log file. For details see reproduce code.

Reproduce code:
---------------
I was performing periodical stress testing by running "ab.exe -c 30 -n 10000 http://..../myframeworkpage.php". Sometimes PHP error log had been empty after 10000 req. stress testing, sometimes there were some errors/warning (from 1 to 10 usually), and these errors were reporting absurd things happening around. What i noticed, is that under heavy load PHP manages to implicitly reset some variables to NULL value, or "lose value", which results in NULL value. Here are some of those errors and my assumptions for those errors:

1) "PHP Notice:  Use of undefined constant  - assumed '' in ... on line 25", where line #25 is "$Db->connect( DB_PERS, DB_HOST, DB_PORT, DB_USER, DB_PASS);". This is definately failure of PHP.

2) "PHP Fatal error:  Call to a member function fetch() on a non-object in ... on line 611" where line #611 is "while( $this->Db->fetch( $this->query));". Please note that this cycle is contained within __destruct() method (probably $Db property is being destroyed too soon when php execution ends, but i assume it was also implicitly reset to NULL). Definately failure of PHP.

3) "PHP Warning:  mysql_pconnect(): Access denied for user 'root'@'localhost' (using password: NO) in ... on line 163" where line #163 is "$link =mysql_pconnect( $host .':' .$port, $user, $pass, $clientFlags);" and $pass was never set to null or whatsoever (it contains string password, always). I could assume here failure of MySQL server.

4) "PHP Warning:  mysql_real_escape_string() expects parameter 2 to be resource, null given in ... on line 483" where line #483 is "return '\'' .mysql_real_escape_string( $data, $this->link) .'\'';" and i'm 100% sure $link property is not null at the step, when call to mysql_real_escape_string is issued. I could assume here failure of MySQL server, which resulted in resource to become null (which is also absurd).

5) "PHP Warning:  Invalid argument supplied for foreach() in ... on line 414" where line #414 is "foreach( $patchset as $blockId =>$fill)", and $patchset is a reference to an array, always. Definately failure of PHP.

And much more such examples, which all say the same thing - "i got the null value, what the hell?".

I tried to make some stress testings for such code as:

class X {
  
  public function __construct() {
    
  }
  
  public function __destruct() {
    
  }
  
  public function test() {
    
  }
  
  public function Y() {
    return new Y( $this);
  }
  
}

class Y {
  
  protected $X;
  
  public function __construct( $X) {
    $this->X =$X;
  }
  
  public function __destruct() {
    $this->X->test();
  }
  
}

$X =new X();
$Y =$X->Y();
$C =$X->Y();
$A =$X->Y();

but it executed successfully with no errors. I tried 300000 requests, everything went fine. But, when i stress-test my framework, PHP *sometimes* acts weird. So far i failed to find compact reproduction code for this issue.

Expected result:
----------------
I expect to see no warnings described in "Reproduce code" section.

Actual result:
--------------
PHP under heavy load implicitly resets random variable's value to NULL or "looses" random variable's value, which results in assignment of NULL value to that variable.

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2009-03-19 00:30 UTC] scottmac@php.net
Thank you for this bug report. To properly diagnose the problem, we
need a short but complete example script to be able to reproduce
this bug ourselves. 

A proper reproducing script starts with <?php and ends with ?>,
is max. 10-20 lines long and does not require any external 
resources such as databases, etc. If the script requires a 
database to demonstrate the issue, please make sure it creates 
all necessary tables, stored procedures etc.

Please avoid embedding huge scripts into the report.

We need an actual reproduce script we can use, I took your small script and ran it under 5.3.0 without issue.

I'm guessing our errors probably all stem from the same issue, if step 1 or 3 happen then 2 and 4 would be expected due to a lack of database connection.
 [2009-03-20 12:12 UTC] ninzya at inbox dot lv
Every mentioned error/warning occurs randomly. It is not possible to trigger these warnings (especially first) during each request, something related to memory fails i think, or stack is being corrupted or something.

I was investigating this problem for past day and i found this. Take a look at the following script:

<?php

function throw_exc() {
  throw new Exception('TEST_EXCEPTION');
}

class Test {
  
  public function __construct() {
    echo 'Constr' ."\n";
  }
  
  public function __destruct() {
    echo 'Destr' ."\n";
  }
  
}

try {
  
  $T =new Test( throw_exc());
  
} catch( Exception $e) {
  echo 'Exception: ' .$e->getMessage();
}

?>

Execution of this script produces:

Destr
Exception: TEST_EXCEPTION

Where it should produce only this part:

Exception: TEST_EXCEPTION

As you can see, destructor is being called, where it shouldn't be. I will make a temporary work around for this situation and investigate further, because i guess this is not the problem that has caused first warning/error message i mentioned above.
 [2009-03-20 12:28 UTC] ninzya at inbox dot lv
I got a little bit closer to the problem. Here's what happens. See this script:

  // load page
  $sql ='SELECT id, ' .implode( ', ', $properties) .'
            FROM ' .TAB_PREF .'pages
          WHERE node_id =' .$Db->escape( $nodeId, 'UINT').'
            AND alt_name =' .$Db->escape( $pageAlt, 'STRING') .'
          LIMIT 0, 1;';
  if(( $row =$Db->queryFirst( $sql)) ===null)
    throw new Exception( 'NOT_FOUND');
    
  if( $row['id'] ===null) {// THIS IS WHERE YOU SHOULD LOOK AT
    trigger_error( 'Got NULL!', E_USER_WARNING);
    trigger_error( 'SQL: ' .$sql, E_USER_WARNING);
    ob_start();
    var_dump( $row);
    trigger_error( ob_get_clean(), E_USER_WARNING);
    die();
  }

As you can see, i am testing if $row['id'] is null (this is unexpected situation when this field becomes null, it's an auto column and at this step i expect successfully fetched row), and if it is NULL, then i wish to see the debug info for the contents of $row and overall "what's going on".

Now here is the error log when this unexpected situation is being RANDOMLY triggered during stress test:

[20-Mar-2009 14:20:37] PHP Warning:  Got NULL! in D:\...\pages.php on line 93

[20-Mar-2009 14:20:37] PHP Warning:  SQL: SELECT id, `title`, `keywords`, `descr`, `template_id`
            FROM pages
          WHERE node_id =11
            AND alt_name ='welcome'
          LIMIT 0, 1; in D:\...\pages.php on line 94

[20-Mar-2009 14:20:37] PHP Warning:  array(5) {
  ["id"]=>
  string(6) "SHARED"
  ["title"]=>
  string(12) "My test page"
  ["keywords"]=>
  string(6) "asdasd"
  ["descr"]=>
  string(6) "asdasd"
  ["template_id"]=>
  string(4) "8567"
}
 in D:\...\pages.php on line 97

I don't know how to explain this. I guess this is mysqlnd failing this bad.
 [2009-03-20 12:34 UTC] ninzya at inbox dot lv
And here is the screenshot of the output of MySql query browser after executing SQL query i mentioned above: http://www.stepanov.lv/pub/mysql.jpg
 [2009-03-21 22:45 UTC] ninzya at inbox dot lv
Tested under PHP 5.2.9-1, the problem does not exist with that version of PHP.
 [2009-03-24 18:49 UTC] kak dot serpom dot po dot yaitsam at gmail dot com
I guess the trouble is in MySQL Native Driver (mysqlnd). No?
 [2009-03-25 07:28 UTC] ninzya at inbox dot lv
In most cases this bug is being hit when working with mysql, and only during high concurrency. When there is no concurrency, or concurrency is not big enough, bug does not trigger. Also i forgot to mention that PHP is installed as a module.

Downloaded QA's PHP 5.3.0RC1 and tested it with both php_mysql and php_mysqli extensions, bug is still present.

The page that is being executed during stress test contains 14 SQL queries, 11 of them are SELECT queries. In most cases the 5th to 7th SELECT queries are failing to return proper data using mysql_fetch_assoc(). When the bug triggers, the values of returned array (usually first on first two keys in the array) are being replaced with irrelevant to SELECT result data, contents of those values are either NULL or data that was stored in DB, but used to appear in previous SQL queries. In the example above you can clearly see that the value of key 'id' in array $row is 'SHARED', but shared is the value of another field in another SQL query i used to execute. Probably, mysqlnd is somehow sharing memory between requests, and somehow memory of another request being processed concurrently is being passed to current request. Another assumption may be that the mysqlnd somehow bypasses update of currently used cached zval.

Anyway, if this is mysqlnd related, still don't know how to explain warning with empty constant mentioned in previous report.
 [2009-03-25 12:19 UTC] kak dot serpom dot po dot yaitsam at gmail dot com
Try to test this under PHP 5.3 with libmysql driver (not mysqlnd).
 [2009-03-25 12:24 UTC] ninzya at inbox dot lv
I guess i can not switch between libmysql and mysqlnd in PHP 5.3. I have no tools to compile PHP myself with the proper library. If you know where i can find PHP 5.3RC1 with libmysql, i would test it out right away.
 [2009-04-07 13:21 UTC] johannes@php.net
Can you give me some more details about your system and the configuration, please.

I spent some time with load testing different configurations now using mysqlnd, pconnects, and PHP's thread-safe mode and wasn't able to reproduce this issue.

Maybe even try to come up with the shortest script possible showing the issue ...
 [2009-04-12 15:08 UTC] ninzya at inbox dot lv
Please excuse me for the delay. I have been figuring out what's the cause of this bug and finally i have something to come up with.

The problem is in mysql_free_result function. Not in itself, but it's behavior has changed. Consider the following example from the PHP manual on mysql_free_result:

Example #1 A mysql_free_result() example
<?php
$result = mysql_query("SELECT id,email FROM people WHERE id = '42'");
if (!$result) {
    echo 'Could not run query: ' . mysql_error();
    exit;
}
/* Use the result, assuming we're done with it afterwards */
$row = mysql_fetch_assoc($result);

/* Now we free up the result and continue on with our script */
mysql_free_result($result);

echo $row['id'];
echo $row['email'];
?>

This code with PHP 5.3.0 with mysqlnd will now fail under high concurrency, because mysql_free_result will affect $row variable and allow another threads to use it's zval to store data. I suspect that mysql_free_result marks the referenced by $row data as 'free' and another threads pick that zval up and work with it. As soon as you release result, another thread may corrupt $row variable. Test this example under high concurrency and you will get different values for $row['id'] and $row['email']. Run 10000 req. test and some of them will fail to produce correct output.

In my framework i use mysql_free_result before referencing last fetched row of result set very often, that's why i hit this bug 'randomly'.

I have another example i just tested, this is part of my framework. The idea is the same - mysql_free_result is being called before actually using fetched data array.

  $con =mysql_pconnect( 'localhost', 'root', '');
  mysql_select_db( 'ewe10');
  $q =mysql_query( $sql ='SELECT id, title, keywords, descr, template_id, `title`, `keywords`, `descr`, `template_id`
            FROM pages
          WHERE node_id =11
            AND alt_name =\'welcome\'
          LIMIT 0, 1;', $con);
  $row =mysql_fetch_assoc( $q);
  mysql_free_result( $q);
  
  if( $row['id'] ===null || $row['template_id'] !=8567 || $row['title'] !='My test page' || $row['keywords'] !='asdasd' || $row['descr'] !='asdasd') {
    trigger_error( 'FAIL!', E_USER_WARNING);
    trigger_error( 'SQL: ' .$sql, E_USER_WARNING);
    ob_start();
    var_dump( $row);
    trigger_error( ob_get_clean(), E_USER_WARNING);
    die('NOT OK');
  }

  die('OK');

SQL for the table:

CREATE TABLE  `ewe10`.`pages` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'unique page id',
  `node_id` int(10) unsigned NOT NULL COMMENT 'node id in which this page is',
  `title` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'page title',
  `keywords` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'page keywords',
  `descr` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'page description',
  `template_id` int(10) unsigned DEFAULT NULL COMMENT 'template this page is using (if NULL, template was deleted from database and this page is stored as a backup)',
  `type` enum('MAIN','PAGE') CHARACTER SET latin1 NOT NULL DEFAULT 'PAGE' COMMENT 'page type',
  `alt_name` varchar(45) CHARACTER SET latin1 NOT NULL COMMENT 'page alternative name',
  `date_add` datetime NOT NULL COMMENT 'when page was added',
  PRIMARY KEY (`id`),
  UNIQUE KEY `NODE_ID_ALT_NAME` (`node_id`,`alt_name`) USING BTREE,
  KEY `FK_pages_templates` (`template_id`),
  KEY `TYPE` (`type`),
  KEY `FK_pages_map` (`node_id`),
  KEY `DATE_ADD` (`date_add`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED;

the only row in this table is shown on the screenshot i referenced in previous report.

To address the issue more quickly, i would like to give you a hint - after calling mysql_free_result there is no possibility to fetch any more rows, so there's nothing to worry about mysql_fetch_*(), but there is possibility that the last fetched row may be referenced. This means - even if mysql_free_result was called, last fetched row must remain locked in mysqlnd internal zval cache until the variable is implicitly/explicitly unset.
 [2009-04-19 12:04 UTC] ninzya at inbox dot lv
Changed bug summary.

My configuration:
  Apache 2.2,
  PHP 5.3.0RC1 as module,
  Windows XP SP3,
  MySQL community server 5.1.33

Bug being hit during high concurrency running apache benchmark: "ab -c 30 -n 10000". See previous posts for details.
 [2009-05-12 09:42 UTC] ninzya at inbox dot lv
This bug is still present in PHP 5.3.0RC2 and is critical to me.
 [2009-06-08 10:36 UTC] andrey@php.net
 Is is possible to provide a package which we can run, even it is not 20-30lines of code, so we can try to reproduce the problem?

In the meanwhile I have committed a change to mysqlnd which I suppose should lead to the problem disappearing - switching the zval cache off. Could you try downloading and testing a binary from
http://windows.php.net/snapshots/

New snapshot should be ready in a few hours. I suppose you will need the VC6 build.

Thank you!
Andrey
 [2009-06-08 12:46 UTC] andrey@php.net
Hi, I switched on the zval cache and tried to reproduce the problem, with no success.
I tested with Apache2 MPM. `ab` concurrency level starting from 20 up to 100. Time from 30 to 45 seconds. I got, with your code, only 3 types of errors. I added mysql_connect() myself
- mysql_connect() fails because cannot have more connections on the unix socket. Using TCP/IP exhausts the resources earlier for connecting to MySQL
- mysql_query() tries to connect, although the connection has failed, typical for mysql_query()! It fails :
-- password problem
-- no resources

I did not see any corruption whatsoever. Difference from you is that I tested on Linux, as server. ab was running on a Mac connected over a FastEthernet switch.

Here follows the code I used:

<?php
$conn = mysql_connect("localhost", "root", "root");
mysql_select_db("test");
$result = mysql_query("SELECT a,b FROM stress_test");
if (!$result) {
    echo 'Could not run query: ' . mysql_error();
    exit;
}
/* Use the result, assuming we're done with it afterwards */
$row = mysql_fetch_assoc($result);

/* Now we free up the result and continue on with our script */
mysql_free_result($result);
var_dump($row);
?>
 [2009-06-08 13:17 UTC] andrey@php.net
Just tried again. 2 million requests, concurrency of 150, which led to load of 90 on the box - pretty hefty, where a problem should show itself and again no problem except that in some cases connection to MySQL couldn't be established, which is to be expected. MySQL was set up to have 1000 maximal connections, so it is not the bottleneck, just system resources.
 [2009-06-08 13:26 UTC] ninzya at inbox dot lv
Hello, Andrey.

Well, the script you are using is not exactly the same i have provided. Try testing for values of $row keys, see if the contained data is the data you expect, as i did in my case. Do not rely on PHPT, try benchmarking locally. I will try my code on linux machine and report in a couple of minutes.
 [2009-06-08 13:52 UTC] ninzya at inbox dot lv
I have just tested my code on linux machine and got no problems. Seems this bug affects only windows build.

I have downloaded latest available PHP 5.3.0RC3 snap from snaps.php.net (PHP 5.3.0RC3-dev (built: Jun  8 2009 14:06:20)), the bug is gone now. If this is your patched snap, then seems you have found the issue. If not, mayble the solar activity has decreased (we're lucky and the bug was fixed by another dev).

Anyway, i'm glad it's working now.
 [2009-06-09 09:53 UTC] andrey@php.net
 Hi,
maybe it is, I think it should be. I did not rely on phpt for testing. I just put it on a Apache2 MPM, Linux, and ran 2 million requests, concurrency of 150, thread-safe build. I asked ab to run with verbosity of 2, and logged the output to a file. Then I did a grep on Conten-Length, then I uniq-ued and sorted the results. Just 5 results. One is correct, others were the errors because of exhausted resources.
 [2009-06-09 09:54 UTC] andrey@php.net
The only change I did was to switch off the zval caching code so every time a zval is needed a new one is created by Zend instead of using our cache.
 [2009-06-09 11:05 UTC] ninzya at inbox dot lv
Well, seems something is wrong with concurrent access to zval cache on windows. Try turning zval cache on and test my code on windows machine.
 [2009-06-09 13:47 UTC] andrey@php.net
Turning on is only possible through a recompilation. Maybe we can add an ini option (INI_SYSTEM) to switch it without recompilation. But currently the CVS is locked for changes.
 [2009-06-10 23:51 UTC] ninzya at inbox dot lv
I guess zval cache is not fixed on windows yet, so i open the bug.
 [2009-06-11 17:30 UTC] andrey@php.net
Do you still experience the problem? You said you don't or you see it again? The zval cache is switched off and there is no way to enable it without recompiling. If the problem persist we have to search for the problem somewhere else.
 [2009-06-11 20:13 UTC] ninzya at inbox dot lv
The problem with turned zval cache off is away, but with turned on zval cache bug still exists, so i assume the bug is NOT fixed, turning zval cache off is a temporary fix, that's why i keep the bug open. Or i shouldn't?
 [2009-06-14 11:31 UTC] ninzya at inbox dot lv
People started reporting memory leaks when working with mysql (through PDO, mysqli, mysql). I didn't try to investigate this issue, but i assume the leaks may have taken place after andrey has switched zval cache to off. You should take a look at this. See some bug reports after andrey has posted about the change he has made to CVS.
 [2009-06-15 10:14 UTC] andrey@php.net
Can you be more specific about these memory leaks?
 [2009-06-15 10:33 UTC] ninzya at inbox dot lv
For example, this one

http://bugs.php.net/bug.php?id=48525

I have seen some more bug reports just after you have turned off zval cache and committed to CVS, i can't find them right now.
 [2009-06-19 08:13 UTC] ninzya at inbox dot lv
Changed bug summary. zval cache is still not fixed on windows. Changed bug status to "Open".
 [2009-06-22 00:50 UTC] pajoye@php.net
See my comment in the other bug, there was no bug but only a wrong impression that the memory usage has been increased.
 [2009-09-24 20:04 UTC] uw@php.net
From our earlier benchmarking I recall that zval caching does give a big performance boost if any at all. We might even remove it altogether. So, potential bug in code that may be removed...
 [2010-01-25 18:02 UTC] andrey@php.net
Thank you for your bug report. This issue has already been fixed
in the latest released version of PHP, which you can download at 
http://www.php.net/downloads.php

Already fixed in 5.3.0
The zval cache was disabled and later removed.
 [2010-09-25 00:37 UTC] advantlee at gmail dot com
We provide the nation’s leading resource and educator on the importance and unique value of diesel engines, fuels,equipment and emissions control technology. An active partner with government, industry and environmental 
groups,the essential uses, environmental advancements and expanding role of the diesel engine as a sustainable energy technology.<a href="http://www.lowmileageengines.com/used-engines/nissan/altima_engine.php">Nissan Altima engine</a>
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Dec 22 21:01:29 2024 UTC