[00:01] timeouts is like balancing a Jenga tower during an earthquake. One shaky external service and the whole thing collapses. Timeouts are way too high by default. They are not affected by max execution time and code often forgets to handle [00:16] connection timeouts. In this video, I'm going to talk about how you can application to keep control during failure scenarios. Mo, I'm Benjamin and PHP performance is my main focus for the last 10 years, helping thousands of [00:31] developers, companies, and open source projects along the way. Why even care about timeouts and achieving fast failures? Because PHP can only handle a limited amount of parallel requests at the same time configured in Apache or [00:45] PHP FBM. When you have enough of them waiting for a long time because of timeouts, then at some point other perfectly fine requests will be rejected at the web server level because there's no available capacity to process them. [01:01] Let's review the status quo of timeouts in PHP. In general, timeouts are controlled by the PHP ini setting, default socket timeout, which is at an default socket timeout, which is at an incredible high 60 seconds by default. [01:16] This applies to file get contents fop dump document soap client and more internal APIs. Let's look at some code to understand it. This 60-cond default applies to internal functions that are using streams such as fet contents fop [01:35] using streams such as fet contents fop document or the soap client. curl is supplying no timeout by default and if the back end is hanging then also the PHP request would wait indefinitely for it but you would be wrong to rely on max [01:49] it but you would be wrong to rely on max execution time to rescue you. The 30 seconds default is not applying to uh IO functions. First uh if an IO function is [02:01] functions. First uh if an IO function is running for a long time then the Zent uh and PHP virtual machine is not actually getting to a state where it checks that the execution time is reached and aborting the script. Second, even on [02:15] aborting the script. Second, even on Linux the max execution time is measured in CPU time and not in wall time. That means if you're waiting for IO the um max execution time is not actually increasing. It's best to look at some [02:30] code to understand how this uh works. So we have this example here and I want to explain it a little bit. So we we reduce the max execution time to 1 second so that uh we have a short example to test. [02:45] that uh we have a short example to test. I have uh included a timer PHP here. I have uh included a timer PHP here. The timer PHP um is just a simple include that takes the start time uh using the high resolution timing API of [03:00] PHP. Uh if you're not familiar with it, it's uh a new API um that replaces micro time for benchmarking because it uses a more uh a better timer [03:13] because it uses a more uh a better timer uh API from Linux or other systems than microt uses. Then we register a shutdown function. It also takes the high resolution timer. It computes the difference and uh calculates this in [03:27] difference and uh calculates this in seconds. So it's human readable. And what we do then in the script is we run an endless loop. We perform one IO an endless loop. We perform one IO weight operation. Uh sleep is uh an IO [03:42] operation. But this could be a database query or an expensive HTTP call taking a query or an expensive HTTP call taking a long time. And then we uh perform some uh expensive CPU uh weight time uh which [03:56] is the password hash function which encrypts the string into a password and encrypts the string into a password and this is um taking quite some time. this is um taking quite some time. So uh let's run this and see how max [04:08] execution time works here with regard to um IO time. So running the script, we expect it to run just 1 second based on max execution time, but it actually runs 4 seconds. And the reason for this is that it only [04:25] calculates the 1 second of CPU time that the script takes, but there is uh around the script takes, but there is uh around 3 seconds of uh IO weight time through the use function here, which is not counted towards the next execution time. [04:41] And uh therefore if you have scripts that have long running IO then max execution time is not the right way to prevent um scripts from running too long and aborting them. Connection timeouts are the next problem. They are usually [04:56] forgotten in network communication. There are usually two independent timeouts in effect. First the timeout that is measured for readr writing on the connection and then second the time out for the connection itself. So [05:11] waiting for um actually connecting to the machine in um and communicating with the machine in um and communicating with it. Uh let's see how uh this works um in practice. In this script, we set the default socket timeout to 2 seconds. We [05:27] include our timer as before and then we perform file get contents against an address that doesn't exist. So it's not reachable. So when we run the script php [05:40] reachable. So when we run the script php connect time out to then we would expect it to be uh terminated at 2 seconds. That's true. So here this is the That's true. So here this is the connection time. So um [05:54] it waited 2 seconds before it decided that this server is not reachable for local connections. 2 seconds or even 1 second might be way too high. Uh if you [06:06] know that a service is not available in the local network, you can usually know this in like a very few milliseconds. Uh and the timeout uh the connection timeout for this should be configured way lower. By now it should be obvious [06:21] that this slow failure mode by default is bad for application stability during a failure. For user experience reasons, you want the users to see a failure page quickly enough, not after 60 seconds. And for capacity reasons, you want to [06:36] keep your PHP process free for handling requests that are not relying on the requests that are not relying on the unavailable services. As a first step, you should therefore reduce the global timeouts to a more manageable number of [06:49] 5 to 10 seconds. What makes you feel comfortable? You can change this in code like I'm showing here using ini set function or globally in the php.ini. To reduce the time out for file get [07:05] contents, you need to use the stream context create function to create a context create function to create a context and you specify HTTP uh options here. So you can specify the timeout in this case to be 1 second or even below 1 [07:21] second. So let's say half a second. However, be careful um because of TCP uh inner workings uh Linux and how PHP works. uh a very low time out might not [07:34] actually be recognized and um there's let's say one option TCP no delay that is not set by default which might be required to be set here as well to get low timeouts. So then uh we have the f get contents [07:48] call against uh a local address that's not available. Let's run this. PHP not available. Let's run this. PHP reduce file get contents time out. And there we see the script uh stops after um half a second. So the time out [08:05] after um half a second. So the time out here works um perfectly fine. If you are making HTTP calls through the DOM document API, then you also need to use stream uh context create again HTTP set a timeout and then you need to pass [08:21] this context to lip XML set stream context and then it's being used for outgoing HTTP requests. So if you load a remote document with uh DOM document API then this will work. So, PHP reduce [08:36] document and we see um there's the timeout stopping the script after 1 timeout stopping the script after 1 second again uh against an endpoint that is not available. For the SOAP client, it's similar but different because the [08:50] SOAP client is not using the PHP streams API internally. We cannot configure it using uh a stream context. Instead we have to use the default socket timeout for the read write timeout and then we have to set a connection time out using [09:06] the SOAP options and uh then again making a call against an an endpoint making a call against an an endpoint that is not available not reachable. that is not available not reachable. Then we find out that uh we have the [09:20] error could not connect to host after 1 second. So the script is supported. Continuing with timeouts for curl, you need to set curl opt out for the read write timeout and you need to set curl [09:36] opt connection time out or connect time out for the connection timeout. So these out for the connection timeout. So these are configured separately uh compared to u the stream context API where the timeout is used for both settings. [09:51] uh then we can determine that a timeout happened. If the HTTP code is uh zero then the script was aborted. We don't have a um response. So let's see and [10:04] have a um response. So let's see and test this So we see here after 1 second uh the timeout happened. We see the error [10:18] message. If you need more control over the timeouts in curl, there are also options that allow you to configure the timeout in milliseconds. So, let's say timeout in milliseconds. So, let's say 2,00 milliseconds for readr timeout and [10:32] 1,000 milliseconds for the connection timeout. the script uh terminates. Last but not least, uh I want to show how to [10:46] configure timeouts for the P reddus library because it's quite widespread. library because it's quite widespread. You need to set um the option time out and then read write timeout. So timeout is the connection timeout. Read write [10:59] is the connection timeout. Read write timeout is the timeout uh that applies when the connection is already present but uh nothing is happening on it. And then in uh this case also we set TCP no delay to true. Otherwise for a very low [11:13] timeout uh we have seen that it doesn't work uh this way. It takes way longer to work uh this way. It takes way longer to uh um like stop the script if the timeout is reached. So let's see how this works in practice. We run the [11:30] reduce prediscript and then we can see after zero uh six uh 260 milliseconds the script is stopped with an error and if we go up here then [11:44] with an error and if we go up here then we can see operation timed out. So this is um the error happening on the PHP stream when the timeout is reached. I hope this was some helpful advice on timeouts in PHP for you. I also have a [11:59] video on max execution time on this channel that you should watch. And newsletter. The link is in the description. Bye.