How Does PHP Max_Execution_Time Work

How does PHP max_execution_time work?

The rules on max_execution_time are relatively simple.

  • Execution time starts to count when the file is interpreted. Time needed before to prepare the request, prepare uploaded files, the web server doing its thing etc. does not count towards the execution time.

  • The execution time is the total time the script runs, including database queries, regardless whether it's running in loops or not. So in the first and second case, the script will terminate with a timeout error because that's the defined behaviour of max_execution_time.

  • External system calls using exec() and such do not count towards the execution time except on Windows. (Source) That means that you could run a external program that takes longer than max_execution_time.

  • When called from the command line, max_execution_time defaults to 0. (Source) So in the third case, your script should run without errors.

  • Execution time and memory usage have nothing to do with each other. A script can run for hours without reaching the memory limit. If it does, then often due to a loop where variables are not unset, and previously reserved memory not freed properly.

Does PHP max_execution time resets on each function call?

The max_execution_time is counted from request start to end. It does not reset when calling a new function.

However, reading the documentation over at https://www.php.net/manual/en/info.configuration.php#ini.max-execution-time
will tell you

The maximum execution time is not affected by system calls, stream operations etc. Please see the set_time_limit() function for more details.

So I guess your calls out to the mailing service don't count towards the total execution time, hence your script can run for 7-8 minutes before finally hitting the PHP runtime limitation.

So, to circumwent your problem with the script running into timeouts when sending emails,
you could either increase the max_execution_time if you have access to the php.ini file or if your PHP Configuration allows ini_set, you can increase the limit manually for this specific script.

Maybe you could even use set_time_limit to increase your maximum runtime every time you've successfully sent out one email.

Another, more complex but also more robust, solution would be to implement some kind of email queue, which is then perpetually processed by a cli script via cron.
This way, you don't have to worry about exceeding the script runtime.
You could put all information about the mails you need to send into a file, or database table, and your cron script could read those jobs and execute them.

What does setting max_execution_time to -1 do?

It sets the maximum execution time for a script to 2**32-1 (or perhaps 2**64-1) seconds, which is a reasonable approximation of "forever". Note that the actual value to make a script run forever is 0.

Tracking the script execution time in PHP

On unixoid systems (and in php 7+ on Windows as well), you can use getrusage, like:

// Script start
$rustart = getrusage();

// Code ...

// Script end
function rutime($ru, $rus, $index) {
return ($ru["ru_$index.tv_sec"]*1000 + intval($ru["ru_$index.tv_usec"]/1000))
- ($rus["ru_$index.tv_sec"]*1000 + intval($rus["ru_$index.tv_usec"]/1000));
}

$ru = getrusage();
echo "This process used " . rutime($ru, $rustart, "utime") .
" ms for its computations\n";
echo "It spent " . rutime($ru, $rustart, "stime") .
" ms in system calls\n";

Note that you don't need to calculate a difference if you are spawning a php instance for every test.

Real max_execution_time for PHP on linux

This is quite a tricky advice, but it will definitely do what you want, if you are willing to modify and recompile PHP.

Take a look at the PHP source code at https://github.com/php/php-src/blob/master/Zend/zend_execute_API.c (the file is Zend/zend_execute_API.c), at function zend_set_timeout. This is the function that implements time limit. Here's how it works on different platforms:

  • on Windows, create a new thread, start a timer on it, and when it finishes, set a global variable called timed_out to 1, the PHP execution core checks this variable for every instruction, then exits (very simplified)

  • on Cygwin, use itimer with ITIMER_REAL, which measures real time, including any sleep, wait, whatever, then raise a signal that will interrupt any processing and stop processing

  • on other unix systems, use itimer with ITIMER_PROF, which only measures CPU time spent by the current process (but both in user-mode and kernel-mode). This means waiting for other processes (like MySQL) doesn't count into this.

Now what you want to do is to change the itimer on your Linux from ITIMER_PROF to ITIMER_REAL, which of course you need to do manually, recompile, install etc. The other difference between these two is that they also use different signal when the timer runs out. So my suggestion is to change the ifdef:

#   ifdef __CYGWIN__

into

#   if 1

so that you set both ITIMER_REAL and the signal that PHP waits for to SIGALRM.

Anyway this whole idea is untested (I use it for some very specific system, where ITIMER_PROF is broken, and it seems to work), unsupported, etc. Use it at your own risk. It may work with PHP itself, but it could break other modules, in PHP and in Apache, if they for whatever reason, use the SIGALRM signal or other timer.



Related Topics



Leave a reply



Submit