You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We have a redis server, we're using the phpredis extension to connect to it. The phpredis extension is built on top of php's stream sockets. The bug I'm reporting can be duplicated without the phpredis extension.
Sometimes, a redis server will timeout during the ssl negotiation. This will generate a warning from php_openssl_enable_crypto(). If there is an error handler that stops execution on warnings, then the underlying connection will be left in place, even without SSL negotiation succeeding. We can see this by looking at client list on the redis server. The connection will still be hanging around, for as long as the fpm process exists.
For testing purposes, we can disable tls on the redis server, which will force the "handshake timed out" results.
Is there a reason that the connection is maintained even if the ssl negotiation fails? Could/should php close the connection when ssl negotiation fails?
Just FYI I have got this on my list with relatively high priority. Although there's bunch of other things so it might take few months to get this resolved.
I assume it's this, right? phpredis/phpredis#1726 . After reading it, it seems that it's specific to TLS 1.3. Can you confirm that it's just related to TLS 1.3? Also does this happen with the latest Redis server version?
Sorry, this is long. tl;dr: I am proposing that the internals stream code should close connections where the tls handshake has timed out before emitting a warning.
For some context, I don't think the issue I'm seeing is the same as phpredis/phpredis#1726
How we found this issue was that we're moving to redis as a session store. We're using persistent connections to limit the number of new connections that need to be created, however, we still sometimes see bursts of new fpm workers coming online, triggering 500 or more new tls-enabled connections to redis. Redis does not like this. It starts to bog down, and ssl connections start to take longer and longer, eventually hitting our timeout (which we set somewhat aggressively at .2 seconds). In this case, the timeout is not a bug with php or redis, but a capacity problem at the redis server. But there is some strange behavior, in that the connections where phpredis hit the timeout while negotiating the tls handshake, are not closed until the fpm process exits. This meant each new request created a new connection, failed tls, but left that connection open, resulting in 20k open connections on a redis host.
This was because the stream internals code emits a warning when the tls handshake times out, and then returns the connection to the phpredis extension, and the phpredis extension attempts to work with the connection, notices communication problems, and closes the connection, except if an error handler exists that stops execution in the case of warnings. In that case, the connection is not closed.
I took a closer look on this one and the problem is basically that there is an area of code in _php_stream_xport_create (specifically everything after stream is created and freed:
/* failure means that they don't get a stream to play with */
if (persistent_id) {
php_stream_pclose(stream);
} else {
php_stream_close(stream);
}
stream=NULL;
}
) where it's not safe to trigger error as it can call error handler which exits so the stream cannot be cleaned up which is especially issue for persistent streams like it's the case here.
I think it's not very practical handle it in every function that triggers error but instead store the errors and emit them later (after freeing the stream). The good thing that we have got already in core (used for inheritance) some functionality to record errors so I will take a look if we could modify that slightly so we can do just record and then reply it after the stream is cleaned up.
Ah yeah forgot about generators. You right I should have used zend_try, zend_catch, zend_bailout here as that's what is used elsewhere. It's just the test is a bit harder but might actually do an FPM test for this which should work.
Activity
cmb69 commentedon Apr 21, 2022
This looks related to https://bugs.php.net/bug.php?id=79501.
staabm commentedon Apr 21, 2022
OT: it has to be related.. it was posted on the very same day - 2 years ago ;-)
bukka commentedon Apr 29, 2022
Just FYI I have got this on my list with relatively high priority. Although there's bunch of other things so it might take few months to get this resolved.
jlesueur commentedon Apr 29, 2022
Thanks for the update, let me know if there's something I can do to make duplication easier.
bukka commentedon Apr 29, 2022
I assume it's this, right? phpredis/phpredis#1726 . After reading it, it seems that it's specific to TLS 1.3. Can you confirm that it's just related to TLS 1.3? Also does this happen with the latest Redis server version?
jlesueur commentedon Apr 29, 2022
I ran this to check if tls version matters:
Sorry, this is long. tl;dr: I am proposing that the internals stream code should close connections where the tls handshake has timed out before emitting a warning.
For some context, I don't think the issue I'm seeing is the same as phpredis/phpredis#1726
How we found this issue was that we're moving to redis as a session store. We're using persistent connections to limit the number of new connections that need to be created, however, we still sometimes see bursts of new fpm workers coming online, triggering 500 or more new tls-enabled connections to redis. Redis does not like this. It starts to bog down, and ssl connections start to take longer and longer, eventually hitting our timeout (which we set somewhat aggressively at .2 seconds). In this case, the timeout is not a bug with php or redis, but a capacity problem at the redis server. But there is some strange behavior, in that the connections where phpredis hit the timeout while negotiating the tls handshake, are not closed until the fpm process exits. This meant each new request created a new connection, failed tls, but left that connection open, resulting in 20k open connections on a redis host.
This was because the stream internals code emits a warning when the tls handshake times out, and then returns the connection to the phpredis extension, and the phpredis extension attempts to work with the connection, notices communication problems, and closes the connection, except if an error handler exists that stops execution in the case of warnings. In that case, the connection is not closed.
bukka commentedon Aug 7, 2022
I took a closer look on this one and the problem is basically that there is an area of code in
_php_stream_xport_create(specifically everything after stream is created and freed:php-src/main/streams/transports.c
Lines 130 to 182 in 80197c5
I think it's not very practical handle it in every function that triggers error but instead store the errors and emit them later (after freeing the stream). The good thing that we have got already in core (used for inheritance) some functionality to record errors so I will take a look if we could modify that slightly so we can do just record and then reply it after the stream is cleaned up.
8 remaining items
bukka commentedon Aug 14, 2022
Ah yeah forgot about generators. You right I should have used
zend_try,zend_catch,zend_bailouthere as that's what is used elsewhere. It's just the test is a bit harder but might actually do an FPM test for this which should work.Re-fix GH-8409: SSL handshake timeout persistent connections hanging
Merge branch 'PHP-8.0' into PHP-8.1
Merge branch 'PHP-8.1'
[skip ci] Add missing NEWS entry for GH-8409 fix to PHP-8.1 branch (s…