PHP:set_time_limit()似乎完全没有影响

9
我正在尝试在普通的PHP Apache脚本(非CLI)中使用PHP的set_time_limit()函数来限制其最大运行时间。它不会对系统调用或类似的东西进行任何操作,我们没有使用安全模式(我们是服务器的所有者但不是管理员)。这是在Linux(Ubuntu)上的PHP 5.2.4。
简单测试案例:
ini_set('max_execution_time', 1);
set_time_limit(1);
$i=0;
while ($i++<100000000000) {} // or anything other arbitrary that takes time
die('Done');

期望结果: 与脚本执行时间超时有关的内容

实际结果: 打印了“完成”。

我是不是做错了什么显而易见的事情?(顺便说一下,是的,脚本确实需要并且可以运行太长时间并且需要被中止,这是不幸的无法避免的。这不是重点。)


@StrikerNL,Mark Baker(下面)是正确的。在Windows上,您将收到超过最大执行时间的错误(因为sleep()时间在Windows中计数),但在Linux中不会。 - Cogicero
1
@StrikerNL 也许你的服务器太强大了。脚本需要多长时间?尝试在顶部和底部使用 microtime() 进行调试。 - eisberg
添加零似乎并没有真正帮助。我认为我遇到了某种错误,在我的本地开发服务器上,一切都按预期工作。也许我需要考虑将PHP升级到更高版本,或者使用register_tick_function()或类似的东西来实现一些功能。 - StrikerNL
是的,也许吧。不过我正在运行5.2.4版本,所以那个漏洞修复应该已经包含在内了。或者可能有类似的问题? - StrikerNL
无法似乎正确格式化,但两者都为空:$ cat /etc/php5/apache2/php.ini | grep disable_ disable_functions = disable_classes = - StrikerNL
显示剩余4条评论
6个回答

5

从手册中引用:

注意:

set_time_limit()函数和配置指令max_execution_time只影响脚本本身的执行时间。任何在脚本执行之外发生的活动所花费的时间,例如使用system()进行系统调用、流操作、数据库查询等,不计入确定脚本运行最长时间的时间。但在Windows上,测量的时间是实际的。

sleep()是那些不会影响脚本运行时间的函数之一。

请参见MPHH在手册中关于sleep()函数的评论:

注意:set_time_limit()函数和配置指令max_execution_time只影响脚本本身的执行时间。任何在脚本执行之外发生的活动所花费的时间,例如使用system()进行系统调用、sleep()函数、数据库查询等,不计入确定脚本运行最长时间的时间。


是的,没错。请看修订过的版本。对此我感到抱歉。 - StrikerNL

2

无论是max_execution_time(...)还是ini_set('max_execution_time',...)都不能计算sleep()、file_get_contents()、shell_exec()、mysql_query()等的时间成本,因此我编写了以下函数my_background_exec()来在后台/分离进程上运行静态方法,当时间到期时,自动杀死它:

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);
           }
}
?>

2

在一些系统中,出于安全和资源共享的考虑,ini_set和类似的函数被禁用。请与您的管理员核实是否存在此类限制。


不,我认为所有功能都已启用,不应该有任何限制。我们拥有服务器,但不管理它。 - StrikerNL
无论您是否拥有服务器,建议进行双重检查。优秀的系统管理员会首先尽可能地加强安全锁定,然后根据需要逐渐放宽安全措施。 - code_burgar

1

这是因为函数 sleep 被时间限制所忽略,当 sleep 被激活时,它会停止计时,当它被取消激活时,它会重新开始计时。


是的,没错。请看修订版。 - StrikerNL
抱歉,我尝试运行您修改后的脚本,但我没有看到“完成”被打印出来,我无法复制这个错误。很抱歉,我得放弃这个了。:-/ - Kristoffer la Cour

1

尝试让您的脚本实际执行一些操作,因为睡眠时间不计入执行时间,因为当脚本正在睡眠时,它不会执行。

编辑:请查看此错误报告http://bugs.php.net/37306,最后一条评论是在“CVS HEAD、PHP_5_2和PHP_5_1”中修复了该问题。所以也许您的PHP版本存在此错误。也许您可以尝试更改max_input_time


是的,没错。请看修订版。对此很抱歉。 - StrikerNL

0

通过检查php.ini中的disable_functions标志来检查您的ini_set是否有效。如果未禁用,则在ini_set('max_execution_time',1)之上立即输出phpinfo()。如果在本地列下更改了该值,则知道ini_set有效。否则,您的脚本没有设置任何最大执行时间。


是的,根据phpinfo()显示已经更改了。但似乎它并没有遵守这个更改。 - StrikerNL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接