Bug #80412 [Opn]: PHP-FPM : PDO / Broken Pipe when DBMS ends the connection
| From: | heavy-traffic-website at yopmail dot com | Date: | Wed, 25 Nov 2020 20:01:27 +0000 |
| Subject: | Bug #80412 [Opn]: PHP-FPM : PDO / Broken Pipe when DBMS ends the connection | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-230620@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=80412&edit=1
ID: 80412
User updated by: heavy-traffic-website at yopmail dot com
Reported by: heavy-traffic-website at yopmail dot com
Summary: PHP-FPM : PDO / Broken Pipe when DBMS ends the
connection
Status: Open
Type: Bug
Package: PDO Core
Operating System: Debian 11
PHP Version: 7.4.12
Block user comment: N
Private report: N
New Comment:
Description:
------------
Related to this very old bug reported 10 years ago
=> https://bugs.php.net/bug.php?id=53287
(johannes@php.net) was wrong - the bug still exists and no one fixed it.
--> Please consider the bug onto the PHP side, not the DBMS side.
PHP (php7.4-fpm) have threads with broken persistent connections cached with PDO and this should try
to reconnect a new one connection rather than calling the error handler in the case where the pipe
is already broken, during opening a nwe connection using persistence. Maybe there is a leak, why
don't you free a broken pipe and try to reuse it ?
Also, the PDO::_construct() return a PDO objet (without throwing an Exception), but PHP rises an
E_NOTICE :
string(68) "PDO::__construct(): send of 5 bytes failed with errno=32 Broken pipe"
That seems to deal with a E_CORE_WARNING ; generated by the core of PHP.
Currently, there is NO SOLUTION UNLESS FORGET PERSITANCE, it sucks !
Test script:
---------------
ini_set("error_reporting", (string) E_ALL);
ini_set("display_errors", "On");
ini_set("display_startup_errors", "On");
ini_set("track_errors", "On");
function my_error($errno, $errstr, $errfile, $errline)
{
var_dump("------------[ ERROR ]-------------");
var_dump("-- errstr: " . $errstr);
var_dump("----------------------------------");
}
set_error_handler("my_error");
abstract class Database
{
private static ?PDO $_db = null;
final public static function test() : ?PDO
{
for ($i = 1 ; $i <=6 ; ++$i)
{
var_dump("<BEFORE new PDO(), loop $i>");
try {
self::$_db = new PDO("mysql:host=127.0.0.1;dbname=XXX",
"XXX", "XXX", [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
}
catch (PDOException $e) {
var_dump("----------[ EXCEPTION ]-----------");
var_dump("-- excstr: " . $e->getMessage());
var_dump("----------------------------------");
}
var_dump("<AFTER new PDO(), loop $i>")
var_dump(self::$_db);
var_dump("<PING select, loop $i>");
var_dump(self::$_db->query("SELECT 1")->rowCount());
}
return self::$_db;
}
}
Database::test();
Expected result:
----------------
string(26) "<BEFORE new PDO(), loop 1>"
string(25) "<AFTER new PDO(), loop 1>"
object(PDO)#2 (0) {
}
string(21) "<PING select, loop 1>"
int(1)
string(26) "<BEFORE new PDO(), loop 2>"
string(25) "<AFTER new PDO(), loop 2>"
object(PDO)#4 (0) {
}
string(21) "<PING select, loop 2>"
int(1)
string(26) "<BEFORE new PDO(), loop 3>"
string(25) "<AFTER new PDO(), loop 3>"
object(PDO)#3 (0) {
}
string(21) "<PING select, loop 3>"
int(1)
string(26) "<BEFORE new PDO(), loop 4>"
string(25) "<AFTER new PDO(), loop 4>"
object(PDO)#2 (0) {
}
string(21) "<PING select, loop 4>"
int(1)
string(26) "<BEFORE new PDO(), loop 5>"
string(25) "<AFTER new PDO(), loop 5>"
object(PDO)#4 (0) {
}
string(21) "<PING select, loop 5>"
int(1)
string(26) "<BEFORE new PDO(), loop 6>"
string(25) "<AFTER new PDO(), loop 6>"
object(PDO)#3 (0) {
}
string(21) "<PING select, loop 6>"
int(1)
Actual result:
--------------
< I AM DOING SOME TESTS NOW >
< Please wait for an update >
Previous Comments:
------------------------------------------------------------------------
[2020-11-25 15:22:18] heavy-traffic-website at yopmail dot com
Some precisons :
ini_set("error_reporting", (string) E_ALL);
ini_set("display_errors", "On");
ini_set("display_startup_errors", "On");
ini_set("track_errors", "On");
set_exception_handler("my_exception");
set_error_handler("my_error");
[ERROR HANDLER]
errno: int(8)
errstr: string(68) "PDO::__construct(): send of 5 bytes failed with errno=32 Broken pipe"
errfile: string(31) "/var/www/XXX/Database.class.php"
errline: int(28)
line 27 --> $db = new PDO("mysql:host=127.0.0.1;dbname=XXX", "XXX",
"XXX",
line 28 --> PDO::ATTR_PERSISTENT => true,
line 29 --> PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
------------------------------------------------------------------------
[2020-11-25 14:24:37] heavy-traffic-website at yopmail dot com
Thank you for your note.
The debate is not about the log itself, independantly the state of error_reporting is. For
developpment, mine is set to E_ALL and works fine, there is no error implementing that.
PHP can procude any warning that he wants, but fail to __contruct PDO after a broken pipe IS NOT A
NORMAL sence or wanted. You cannot tell each pipe connection will never end, so you made a mistake
in your configuration... or that is a desired effect.
There is no error somewhere due to the other parties. It can happen of a pipe is broken due to long
inactivity ; do network activities and you will know. Softwares have to retry retablishing the pipe
BEFORE throwing any error : it should be the same with PHP/PDO core.
If PHP/PDO fails to send data ; it is because the pipe onto the thread is cached (due to the
persistance), and reused like it should be always be working, it is incorrect.
When PHP/PDO have a "send of 5 bytes failed with errno=32", you should flush the cache
about persistent connexion then RETRYING __construct with persistant connection LIKE it has been a
new thread without cached yet.
If an error had to be throwed, it can be adjusted :
- "send of 5 bytes failed with errno=32", moreover, "server has gone away".
- "send of 5 bytes failed with errno=32", moreover, "[last connection error]".
This error will be consistent. But to let a thread with inconsistent broken pipe cached, is not
consistent neither wanted neither normal. By the way, you can't have 2 identical "Pipe
error" when retrying connection. So, it is logic to retry ONCE a persistent broken pipe.
------------------------------------------------------------------------
[2020-11-25 08:44:13] nikic@php.net
Just a note, if you see that "broken pipe" error, that probably means you have a broken
error handler implementation somewhere. This error is suppressed internally (error_reporting=0), but
an incorrectly implemented error handler that does not check the error_reporting level may make it
visible again.
------------------------------------------------------------------------
[2020-11-24 23:38:54] heavy-traffic-website at yopmail dot com
It doesn't need any strategy or interval or whatever. Just to connect like the first persistent
connexion when the pipe is broken. That's all.
Independantly what is the DBMS used, even if Haproxy break the pipe.
------------------------------------------------------------------------
[2020-11-24 23:23:09] heavy-traffic-website at yopmail dot com
Thank you for your review.
The code PDO::__contruct() is very explicit : I want a persistent connection. And this line works
until the pipe exists.
Maybe not everybody use HAproxy between Galera Cluster and PHP-FPM but, the code to keep it working
without error is really poor :
try{ new PDO() }catch(){ new PDO() }
Please don't justify this is normal, when the pipe broke whatever the reason is : PHP/PDO
should DETECT the broken PIPE and treat the __construct like no one persistent connection exists yet
inside each thead.
We are on the ends of 2020, it has to think logic and serve the langage, not saying like 10 year ago
: it is still a feature.
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=80412
--
Edit this bug report at https://bugs.php.net/bug.php?id=80412&edit=1