Bug #79375 [NEW]: mysqli_store_result does not report error from lock wait timout
| From: | brett dot cundal at iugome dot com | Date: | Thu, 12 Mar 2020 21:07:49 +0000 |
| Subject: | Bug #79375 [NEW]: mysqli_store_result does not report error from lock wait timout | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-226074@lists.php.net to get a copy of this message | ||
From: brett dot cundal at iugome dot com
Operating system: Ubuntu 18.04
PHP version: 7.2.28
Package: MySQLi related
Bug Type: Bug
Bug description:mysqli_store_result does not report error from lock wait timout
Description:
------------
We've run across a case where mysqli_store_result() returns an empty
result set when it should set the error code and message. In this case
it's happening with a prepared and executed statement which is trying to
read a row/gap locked by another connection.
Checking the return code from store_result() does not indicate the
error, and I'm not able to find any way to get the error to be
indicated. Doing the same flow using mysqli_get_result() does result in
the error being reported correctly.
Note that I'm actually testing with PHP 7.2.24 which is not the latest
at this time (it's the latest on ubuntu 18.04), but I've checked the
change log for the few versions since this, and I don't see anything
likely to fix this between 7.2.24 and 7.2.28.
Possibly the issue is more broad, e.g. any time the error would be
returned by store_result() rather than immediately by execute() - but we
haven't found any other confirmed cases where this happens.
This seems similar to https://bugs.php.net/bug.php?id=66370 but I ran
that script on php 7.2 and was not able to reproduce that problem using
query(), store_result() or get_result().
The test case is as follows.
Set up a table with a composite key:
CREATE TABLE
lock_test (
first int(10) unsigned NOT NULL,
second int(10) unsigned NOT NULL,
third int(10) unsigned NOT NULL,
status int(10) unsigned NOT NULL,
PRIMARY KEY (first,second,third)
) ENGINE=InnoDB;
Insert a record:
INSERT INTO lock_test (first, second, third, status) VALUES (1, 1, 1,
1);
On one connection, select the record using part of the key, with "FOR
UPDATE":
(we didn't see this problem when specifying the full key and therefore
locking only the one record)
$mysqli->query("START TRANSACTION");
$query = "SELECT status FROM lock_test WHERE first = 1 AND second = 1
FOR UPDATE";
$stmt = $mysqli->prepare($query);
$stmt->execute();
if(!$stmt->store_result())
throw new Exception("Store failed: {$mysqli->error}");
echo "Got {$stmt->num_rows} for $name\n";
This will successfully return one record.
Leave that open, and do the same on another connection.
The second connection will block until 'innodb_lock_wait_timeout'
elapses, and then will successfully return with 0 records in the result
set. Instead, it should return an error, either on execute() or on
store_result(), and should set the mysqli error code.
The attached script assumes the table described above is set up on
localhost in a schema named 'test'. Adjust as needed otherwise.
Test script:
---------------
$firstConnection = getConnection();
selectForUpdateStore($firstConnection, 'first connection');
$secondConnection = getConnection();
selectForUpdateStore($secondConnection, 'second connection');
function getConnection() {
$mysqli = mysqli_init();
$mysqli->real_connect('localhost', 'root', '', 'test');
return $mysqli;
}
function selectForUpdateStore(mysqli $mysqli, string $name) {
$mysqli->query("SET innodb_lock_wait_timeout = 2");
$mysqli->query("START TRANSACTION");
$query = "SELECT status FROM lock_test WHERE first = 1 AND second = 1
FOR UPDATE";
echo "Running query on $name\n";
$stmt = $mysqli->prepare($query);
$stmt->execute();
if(!$stmt->store_result())
throw new Exception("Store failed for $name: {$mysqli->error}");
echo "Got {$stmt->num_rows} for $name\n";
}
Expected result:
----------------
Running query on first connection
Got 1 for first connection
Running query on second connection
PHP Fatal error: Uncaught Exception: Store failed for second
connection: Lock wait timeout exceeded; try restarting transaction [...]
Actual result:
--------------
Running query on first connection
Got 1 for first connection
Running query on second connection
Got 0 for second connection
--
Edit bug report at https://bugs.php.net/bug.php?id=79375&edit=1
--
Fix committed: https://bugs.php.net/fix.php?id=79375&r=fixed
Fixed in release: https://bugs.php.net/fix.php?id=79375&r=alreadyfixed
Need backtrace: https://bugs.php.net/fix.php?id=79375&r=needtrace
Need Reproduce Script: https://bugs.php.net/fix.php?id=79375&r=needscript
Try newer version: https://bugs.php.net/fix.php?id=79375&r=oldversion
Not developer issue: https://bugs.php.net/fix.php?id=79375&r=support
Expected behavior: https://bugs.php.net/fix.php?id=79375&r=notwrong
Not enough info: https://bugs.php.net/fix.php?id=79375&r=notenoughinfo
Submitted twice: https://bugs.php.net/fix.php?id=79375&r=submittedtwice
register_globals: https://bugs.php.net/fix.php?id=79375&r=globals
PHP version support discontinued: https://bugs.php.net/fix.php?id=79375&r=phptooold
Daylight Savings: https://bugs.php.net/fix.php?id=79375&r=dst
IIS Stability: https://bugs.php.net/fix.php?id=79375&r=isapi
Install GNU Sed: https://bugs.php.net/fix.php?id=79375&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=79375&r=float
No Zend Extensions: https://bugs.php.net/fix.php?id=79375&r=nozend
MySQL Configuration Error: https://bugs.php.net/fix.php?id=79375&r=mysqlcfg