Reputation: 91
I'm trying to use PHP's set_time_limit() in a normal PHP apache script (not CLI) to limit its max running time. It's not doing anything with system calls or anything similar, we're not using safe mode (we're the owners but not the admins of the server). This is PHP 5.2.4 on Linux (Ubuntu).
Simple test case:
ini_set('max_execution_time', 1);
set_time_limit(1);
$i=0;
while ($i++<100000000000) {} // or anything other arbitrary that takes time
die('Done');
Expected result: something relating to the script execution time being exceeded
Actual result: Done is printed.
Am I doing something obvious wrong? (By the way, yes, the script really needs to and can run for too long and need to be aborted, it's unfortunately not something that can be avoided. That is not the point here.)
Upvotes: 9
Views: 5756
Reputation: 12935
Both max_execution_time(...) and ini_set('max_execution_time',...) won't count the time cost of sleep(),file_get_contents(),shell_exec(),mysql_query() etc, so i build the following function my_background_exec() to run a static method on the background/detached process and when time is out, kill it automatically:
my_exec.php:
<?php
function my_background_exec($function_name, $params, $str_requires, $timeout=600)
{$map=array('"'=>'\"', '$'=>'\$', '`'=>'\`', '\\'=>'\\\\', '!'=>'\!');
$str_requires=strtr($str_requires, $map);
$path_run=dirname($_SERVER['SCRIPT_FILENAME']);
$my_target_exec="/usr/bin/php -r \"chdir('{$path_run}');{$str_requires}\\\$params=json_decode(file_get_contents('php://stdin'),true);call_user_func_array('{$function_name}', \\\$params);\"";
$my_target_exec=strtr(strtr($my_target_exec, $map), $map);
$my_background_exec="(/usr/bin/php -r \"chdir('{$path_run}');{$str_requires}my_timeout_exec(\\\"{$my_target_exec}\\\", file_get_contents('php://stdin'), {$timeout});\" <&3 &) 3<&0";//php by default use "sh", and "sh" don't support "<&0"
my_timeout_exec($my_background_exec, json_encode($params), 2);
}
function my_timeout_exec($cmd, $stdin='', $timeout)
{$start=time();
$stdout='';
$stderr='';
//file_put_contents('debug.txt', time().':cmd:'.$cmd."\n", FILE_APPEND);
//file_put_contents('debug.txt', time().':stdin:'.$stdin."\n", FILE_APPEND);
$process=proc_open($cmd, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes);
if (!is_resource($process))
{return array('return'=>'1', 'stdout'=>$stdout, 'stderr'=>$stderr);
}
$status=proc_get_status($process);
posix_setpgid($status['pid'], $status['pid']); //seperate pgid(process group id) from parent's pgid
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
while (1)
{$stdout.=stream_get_contents($pipes[1]);
$stderr.=stream_get_contents($pipes[2]);
if (time()-$start>$timeout)
{//proc_terminate($process, 9); //only terminate subprocess, won't terminate sub-subprocess
posix_kill(-$status['pid'], 9); //sends SIGKILL to all processes inside group(negative means GPID, all subprocesses share the top process group, except nested my_timeout_exec)
//file_put_contents('debug.txt', time().":kill group {$status['pid']}\n", FILE_APPEND);
return array('return'=>'1', 'stdout'=>$stdout, 'stderr'=>$stderr);
}
$status=proc_get_status($process);
//file_put_contents('debug.txt', time().':status:'.var_export($status, true)."\n";
if (!$status['running'])
{fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $status['exitcode'];
}
usleep(100000);
}
}
?>
test.php:
<?php
my_background_exec('A::jack', array('hello ', 'jack'), 'require "my_exec.php";require "a_class.php";', 8);
?>
a_class.php:
<?php
class A
{
static function jack($a, $b)
{sleep(4);
file_put_contents('debug.txt', time().":A::jack:".$a.' '.$b."\n", FILE_APPEND);
sleep(15);
}
}
?>
Upvotes: 2
Reputation: 1524
Check if your ini_set works by checking the disable_functions flag in php.ini. If it is not disabled, echo phpinfo() immediately after your ini_set('max_execution_time', 1); above. If the value is changed under the local column, then you know ini_set works. Else, your script has not set any maximum execution time, then.
Upvotes: 0
Reputation: 3196
Try having your script actually do something, sleep time isn't counted towards execution time since while the script is sleeping it's not executing.
EDIT : Check out this bugreport http://bugs.php.net/37306 The last comment is that it was fixed in "CVS HEAD, PHP_5_2 and PHP_5_1." so maybe your PHP version has this bug. Maybe you can you try changing the max_input_time
.
Upvotes: 1
Reputation: 2576
That is because the function sleep is ignored by the time limit, it will stop counting time when sleep is activated and start counting again when it's deactivated.
Upvotes: 1
Reputation: 12323
On some systems ini_set and similar functions are disabled for security and resource sharing reasons. Check with your administrator to see if such restrictions are in place.
Upvotes: 2
Reputation: 212412
Quoting from the manual:
Note:
The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real.
sleep() is among those functions that do not affect the running time of the script
see MPHH's comment on the sleep() page of the manual
Note: The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), the sleep() function, database queries, etc. is not included when determining the maximum time that the script has been running.
Upvotes: 5