在PHP中检测代码块的超时

  • 本文关键字:超时 代码 PHP php timeout
  • 更新时间 :
  • 英文 :


是否有一种方法可以中止代码块,如果它在PHP中花费太长时间?比如:

//Set the max time to 2 seconds
$time = new TimeOut(2);
$time->startTime();
sleep(3)
$time->endTime();
if ($time->timeExpired()){
    echo 'This function took too long to execute and was aborted.';
} 

它不必完全像上面一样,但是有任何本地PHP函数或类做这样的事情吗?

Edit: Ben Lee对pcnt_fork的回答将是完美的解决方案,除了它不适用于Windows。有没有其他的方法来完成这一点与PHP工作在Windows和Linux,但不需要外部库?

编辑2: XzKto的解决方案在某些情况下有效,但不一致,我似乎无法捕获异常,无论我尝试什么。这个用例正在检测单元测试的超时。如果测试超时,我想终止它,然后继续进行下一个测试。

您可以通过fork进程,然后使用父进程来监视子进程来做到这一点。Pcntl_fork是一个分岔进程的方法,因此在内存中有两个几乎相同的程序并行运行。唯一的区别是,在父进程pcntl_fork中,返回一个正整数,对应于子进程的进程id。在另一个进程中,子进程pcntl_fork返回0。

下面是一个例子:

$pid = pcntl_fork();
if ($pid == 0) {
    // this is the child process
} else {
    // this is the parent process, and we know the child process id is in $pid
}

这是基本结构。下一步是添加进程过期。您的内容将在子进程中运行,父进程将只负责监视子进程并为其计时。但是为了让一个进程(父进程)杀死另一个进程(子进程),需要有一个信号。信号是进程通信的方式,表示"你应该立即结束"的信号是SIGKILL。您可以使用posix_kill发送此信号。所以家长应该等待2秒然后杀死孩子,像这样:

$pid = pcntl_fork();
if ($pid == 0) {
    // this is the child process
    // run your potentially time-consuming method
} else {
    // this is the parent process, and we know the child process id is in $pid
    sleep(2); // wait 2 seconds
    posix_kill($pid, SIGKILL); // then kill the child
}

如果您在一个命令上脚本暂停(例如sleep())除了fork之外,您不能真正做到这一点,但是对于特殊情况有很多解决方案:例如异步查询,如果您在DB查询上编程暂停,proc_open如果您在一些外部执行中编程暂停等。不幸的是,它们都是不同的,所以没有通用的解决方案。

如果你的脚本等待一个很长的循环/很多行代码,你可以做一个肮脏的伎俩,像这样:

declare(ticks=1);
class Timouter {
    private static $start_time = false,
    $timeout;
    public static function start($timeout) {
        self::$start_time = microtime(true);
        self::$timeout = (float) $timeout;
        register_tick_function(array('Timouter', 'tick'));
    }
    public static function end() {
        unregister_tick_function(array('Timouter', 'tick'));
    }
    public static function tick() {
        if ((microtime(true) - self::$start_time) > self::$timeout)
            throw new Exception;
    }
}
//Main code
try {
    //Start timeout
    Timouter::start(3);
    //Some long code to execute that you want to set timeout for.
    while (1);
} catch (Exception $e) {
    Timouter::end();
    echo "Timeouted!";
}

但是我不认为它很好。如果你能说明具体情况,我想我们可以更好地帮助你。

这是一个老问题,到目前为止可能已经解决了很多次,但是对于寻找解决这个问题的简单方法的人来说,现在有一个库:PHP Invoker。

如果执行时间超过限制,可以使用declare function。http://www.php.net/manual/en/control-structures.declare.php

下面是如何使用 的代码示例
define("MAX_EXECUTION_TIME", 2); # seconds
$timeline = time() + MAX_EXECUTION_TIME;
function check_timeout()
{
    if( time() < $GLOBALS['timeline'] ) return;
    # timeout reached:
    print "Timeout!".PHP_EOL;
    exit;
}
register_tick_function("check_timeout");
$data = "";
declare( ticks=1 ){
    # here the process that might require long execution time
    sleep(5); // Comment this line to see this data text
    $data = "Long process result".PHP_EOL;
}
# Ok, process completed, output the result:
print $data;

使用此代码,您将看到超时消息。如果你想在declare块中获得Long process结果,你可以删除sleep(5)行或增加在脚本开始时声明的Max Execution Time

如果不是安全模式,那么设置时间限制呢

花了大约两分钟,我忘了打电话给$time->startTime();,所以我真的不知道到底花了多长时间;)

class TimeOut{
    public function __construct($time=0)
    {
        $this->limit = $time;
    }
    public function startTime()
    {
        $this->old = microtime(true);
    }
    public function checkTime()
    {
        $this->new = microtime(true);
    }
    public function timeExpired()
    {
        $this->checkTime();
        return ($this->new - $this->old > $this->limit);
    }
}

演示

我真的不明白你的endTime()调用做什么,所以我做了checkTime()代替,这也没有真正的目的,但更新内部值。timeExpired()自动调用它,因为如果你忘记调用checkTime(),它肯定会很臭,而且它使用的是旧时间。

您也可以使用第二个脚本,其中包含暂停代码,通过超时设置的curl调用执行。另一个明显的解决方案是修复暂停的原因。

我是这样做的。感谢别人的回答:

<?php
class Timeouter
{
   private static $start_time = FALSE, $timeout;
   /**
    * @param   integer $seconds Time in seconds
    * @param null      $error_msg
    */
   public static function limit($seconds, $error_msg = NULL)
   : void
   {
      self::$start_time = microtime(TRUE);
      self::$timeout    = (float) $seconds;
      register_tick_function([ self::class, 'tick' ], $error_msg);
   }
   public static function end()
   : void
   {
      unregister_tick_function([ self::class, 'tick' ]);
   }
   public static function tick($error)
   : void
   {
      if ((microtime(TRUE) - self::$start_time) > self::$timeout) {
         throw new RuntimeException($error ?? 'You code took too much time.');
      }
   }
   public static function step()
   : void
   {
      usleep(1);
   }
}

然后你可以试着这样做:

  <?php
  try {
     //Start timeout
     Timeouter::limit(2, 'You code is heavy. Sorry.');
     //Some long code to execute that you want to set timeout for.
     declare(ticks=1) {
        foreach (range(1, 100000) as $x) {
           Timeouter::step(); // Not always necessary
           echo $x . "-";
        }
     }
     Timeouter::end();
  } catch (Exception $e) {
     Timeouter::end();
     echo $e->getMessage(); // 'You code is heavy. Sorry.'
  }

我用php编写了一个脚本,使用pcntl_fork和lockfile来控制外部调用在超时后执行kill。


#!/usr/bin/env php
<?php
if(count($argv)<4){
    print "nnn";
    print "./fork.php PATH "COMMAND" TIMEOUTn"; // TIMEOUT IN SECS
    print "Example:n";
    print "./fork.php /root/ "php run.php" 20";
    print "nnn";
    die;
}
$PATH = $argv[1];
$LOCKFILE = $argv[1].$argv[2].".lock";
$TIMEOUT = (int)$argv[3];
$RUN = $argv[2];
chdir($PATH);

$fp = fopen($LOCKFILE,"w"); 
    if (!flock($fp, LOCK_EX | LOCK_NB)) {
            print "Already Runningn";
            exit();
    }
$tasks = [
  "kill",
  "run",
];
function killChilds($pid,$signal) { 
    exec("ps -ef| awk '$3 == '$pid' { print  $2 }'", $output, $ret); 
    if($ret) return 'you need ps, grep, and awk'; 
    while(list(,$t) = each($output)) { 
            if ( $t != $pid && $t != posix_getpid()) { 
                    posix_kill($t, $signal);
            } 
    }    
} 
$pidmaster = getmypid();
print "Add PID: ".(string)$pidmaster." MASTERn";
foreach ($tasks as $task) {
    $pid = pcntl_fork();
    $pidslave = posix_getpid();
    if($pidslave != $pidmaster){
        print "Add PID: ".(string)$pidslave." ".strtoupper($task)."n";
    }
  if ($pid == -1) {
    exit("Error forking...n");
  }
  else if ($pid == 0) {
    execute_task($task);        
        exit();
  }
}
while(pcntl_waitpid(0, $status) != -1);
echo "Do stuff after all parallel execution is complete.n";
unlink($LOCKFILE);

function execute_task($task_id) {
    global $pidmaster;
    global $TIMEOUT;
    global $RUN;
    if($task_id=='kill'){
        print("SET TIMEOUT = ". (string)$TIMEOUT."n");
        sleep($TIMEOUT);
        print("FINISHED BY TIMEOUT: ". (string)$TIMEOUT."n");
        killChilds($pidmaster,SIGTERM);
        die;
  }elseif($task_id=='run'){
        ###############################################
        ### START EXECUTION CODE OR EXTERNAL SCRIPT ###
        ###############################################
            system($RUN);
        ################################    
        ###             END          ###
        ################################
        killChilds($pidmaster,SIGTERM);
        die;
    }
}

测试脚本run.php

<?php
$i=0;
while($i<25){
    print "test... $in";
    $i++;
    sleep(1);
}

相关内容

  • 没有找到相关文章

最新更新