Skip to content

Assumed fix for several "Empty row packet body" bug tickets #2131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

Kaiserchen
Copy link

@Kaiserchen Kaiserchen commented Sep 16, 2016

tickets that complain about this:

https://bugs.php.net/bug.php?id=66370
https://bugs.php.net/bug.php?id=64763
https://bugs.php.net/bug.php?id=65825

One is not reliably able to tell that a mysql response was successfully consumed completly without registering an error handler, that listens for the "Empty row packet body" warning. Not any of the examples does this. We were able to reproduce this by taking a long time in processing result rows, and forcing the mysql on the other end into a write timeout, leaving us with an incomplete result.
This points against master. Once there is a narrativ on what is the correct fix. I volunteer in backporting to the appropriate branches.

Best

@Kaiserchen
Copy link
Author

one of the bugs had a patch attached that is probably superior.
https://bugs.php.net/patch-display.php?bug_id=66370&patch=mysqlnd-errno-error-fix.patch&revision=latest

@Fleshgrinder
Copy link
Contributor

Fleshgrinder commented Sep 19, 2016

Definitely needs fixing but the referenced patch looks like the way to go. This should go into 5.6, 7.0, and 7.1 besides master.

I'll bring this up on internals because it seems that none of the maintainers of mysqlnd are active on GitHub.

@Fleshgrinder
Copy link
Contributor

I wrote one of the maintainers directly before opening a thread on internals, will keep you guys updated.

@smalyshev smalyshev added the Bug label Oct 30, 2016
@JesusTheHun
Copy link

any news ?

@Fleshgrinder
Copy link
Contributor

Nobody was able to create a reproducible test case for this so far.

@krakjoe
Copy link
Member

krakjoe commented Jan 3, 2017

The original author of this patch has stated that there is probably a superior solution, for that reason, and because the author seems to have abandoned working on this patch, I'm closing this PR.

Please take this action as encouragement to open a clean PR, with the best possible patch, and try to ensure that there is satisfactory test coverage for the change (I am aware in this case it may be problematic).

@krakjoe krakjoe closed this Jan 3, 2017
@Fleshgrinder
Copy link
Contributor

We have a test case by now and I will take care of a clean PR asap.

@bchecketts
Copy link

I've used this as an easily repeatable test. It sets the mysql session timeout to 3 seconds, sleeps for 5 seconds, so the mysql server will destroy the connection, then attempts to use the connection again. It is not dependent on any database or table structure.

<?php

$conf = [
    'host' => 'myHostname',
    'user' => 'myUsername',
    'pass' => 'myPassword',
    'port' => 3306,
    'name' => 'myDatabaseName',
];

$dbh = new PDO("mysql:host={$conf['host']};port={$conf['port']};dbname={$conf['name']};", $conf['user'], $conf['pass']);
$dbh->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );

$sql = "SELECT 1";
$sth = $dbh->prepare($sql);
$sth->execute();
$sth->setFetchMode(\PDO::FETCH_NUM);
$row =  $sth->fetch();
echo "Got one = {$row[0]}\n";

echo "Setting wait_timeout to 3s\n";
$dbh->query("SET SESSION wait_timeout = 3");

echo "sleeping for 5s\n";
sleep(5);

try {
    $sql = "SELECT 1";
    $sth = $dbh->prepare($sql);
    $sth->execute();
    $sth->setFetchMode(\PDO::FETCH_NUM);
    $row =  $sth->fetch();
    echo "Got one = {$row[0]}\n";

} catch (\Exception $e) {
    $class = gettype($e);
    echo "CAUGHT EXCEPTION {$class}) : ".$e->getMessage()."\n";
}

echo "all done\n";

Expected behavior is that it the output will contain the CAUGHT EXCEPTION line, instead it issues a PHP Warning and continues to execute. Making it impossible to catch and handle this exception:

Got one = 1
Setting wait_timeout to 3s
sleeping for 5s
PHP Warning:  PDOStatement::execute(): MySQL server has gone away in exceptionTest.php on line 30
PHP Warning:  PDOStatement::execute(): Error reading result set's header in exceptionTest.php on line 30
CAUGHT EXCEPTION object) : SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
all done

@SpyroTEQ
Copy link

SpyroTEQ commented Oct 4, 2019

One can still grab the warning with set_error_handler, and turn it into an exception to be catched. I couldn't set up a 100% reproducible test case. The best I could come up so far is:

<?php

$pdo = new PDO(
		'mysql:host=127.0.0.1;dbname=test_sql',
		'test-root',
		'...');

while (true) {
	$connections = $pdo->query("SHOW PROCESSLIST")->fetchAll(PDO::FETCH_ASSOC);
	foreach ($connections as $connection) {
		if ($connection['Info'] === 'SHOW PROCESSLIST') {
			// Don't kill yourself
			continue;
		} else if (strpos($connection['Info'], 'SELECT') !== false) {
			$cmd = "KILL {$connection['Id']}";
			echo "$cmd\n";
			$pdo->exec($cmd);
		}
	}
	usleep(200000);
}

and

<?php
// Trying to reproduce the "Empty row packet body" error from MTA/MDL
// Seems related to https://github.com/php/php-src/pull/2131

$pdo = new PDO(
		'mysql:host=127.0.0.1;dbname=test_sql',
		'test-root',
		'...');

$q = $pdo->prepare("
SELECT *
FROM (
	SELECT '.' AS n
	UNION SELECT 2
	UNION SELECT 3
	UNION SELECT 4
	UNION SELECT 5
	UNION SELECT 6
	UNION SELECT 7
	UNION SELECT 8) AS a
INNER JOIN (
	SELECT 1 AS n
	UNION SELECT 2
	UNION SELECT 3
	UNION SELECT 4
	UNION SELECT 5
	UNION SELECT 6
	UNION SELECT 7
	UNION SELECT 8) AS b ON TRUE
INNER JOIN (
	SELECT 1 AS n
	UNION SELECT 2
	UNION SELECT 3
	UNION SELECT 4
	UNION SELECT 5
	UNION SELECT 6
	UNION SELECT 7
	UNION SELECT 8) AS c ON TRUE
INNER JOIN (
	SELECT 1 AS n
	UNION SELECT 2
	UNION SELECT 3
	UNION SELECT 4
	UNION SELECT 5
	UNION SELECT 6
	UNION SELECT 7
	UNION SELECT 8) AS d ON TRUE
INNER JOIN (
	SELECT 1 AS n
	UNION SELECT 2
	UNION SELECT 3
	UNION SELECT 4
	UNION SELECT 5
	UNION SELECT 6
	UNION SELECT 7
	UNION SELECT 8) AS e ON TRUE
INNER JOIN (
	SELECT 1 AS n
	UNION SELECT 2
	UNION SELECT 3
	UNION SELECT 4
	UNION SELECT 5
	UNION SELECT 6
	UNION SELECT 7
	UNION SELECT 8) AS f ON TRUE");

set_error_handler(static function ($errno, $errstr, $errfile, $errline) {
	/** @noinspection ForgottenDebugOutputInspection */
	var_dump($errno, $errstr, $errfile, $errline);
	throw new LogicException($errstr);
});

ini_set('memory_limit', '2G');
for ($i = 1000; $i > 0; $i--) {
	echo ".";
	if ($q->execute()) {
		echo $q->fetchAll()[0][0];
	}
	flush();
}

By running both files concurrently (1st should be started 1st, then while running, start the 2nd one), chances are you'll get a "Empty Row Packet Body" (or a "Error reading result set's header" if the KILL occured before data headers were received by PDO). If you have ways of making it more reliable, please say so.

Still, if you use PDO::ATTR_ERRMODE at PDO::ERRMODE_EXCEPTION, then you won't catch the Warning, but you will catch a PdoException for the disconnection.

php-pulls pushed a commit that referenced this pull request Oct 28, 2020
Set error_info when we fail to read a packet, instead of throwing
a warning. Additionally we also need to populate the right
error_info in rowp_read -- we'll later take the error from the
packet, not the connection.

No test case, as this is hard to reliably test. I'm using the
test case from:
#2131 (comment)
@nikic
Copy link
Member

nikic commented Oct 28, 2020

For the record, I've applied a66d73d that fixes the issue for the test case in the preceding comment at least, but I'm not sure if it fixes all cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants