加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

php – 当无限循环无效时,脚本对信号没有响应

发布时间:2020-12-13 15:56:51 所属栏目:PHP教程 来源:网络整理
导读:我正在尝试创建一个可重用的通用cli服务器,我可以从终端会话控制(启动/暂停/恢复/停止). 到目前为止,我的方法是,我有一个脚本独立地充当控制台(父循环)和服务器(子循环),而不是通过pcntl_fork() – ing,而是通过proc_open() – 将自身作为子进程,可以这么说.
我正在尝试创建一个可重用的通用cli服务器,我可以从终端会话控制(启动/暂停/恢复/停止).

到目前为止,我的方法是,我有一个脚本独立地充当控制台(父循环)和服务器(子循环),而不是通过pcntl_fork() – ing,而是通过proc_open() – 将自身作为子进程,可以这么说.

然后,控制台循环通过posix_kill()发送信号来作用于服务器循环.

暂时忽略这是否是一种明智的方法,我偶然发现了一些奇怪的事情 – 即,当控制台循环使用SIGTSTP信号暂停服务器循环时,服务器循环将不响应SIGCONT信号,除非它的while循环实际上做了一些有用的事情

这可能会发生什么?

编辑:

根据评论中的要求,我简化了我的代码示例.但是,正如我所担心的那样,这段代码运行得很好.

也许我忽略了代码中的某些东西,但我只是看不出两个例子在他们的例程中有何不同 – 对我而言,两个例子看起来都遵循相同的例程.

并且作为一个重要的旁注:在我更复杂的例子中,我已经尝试不断地写入循环()中的文件,这实际上是有效的,即使暂停时也是如此.所以,这告诉我循环继续正常运行.在我暂停之后,服务器只是不想再响应信号了.

无论如何,这是我之前的示例的简化版本,如下所示:

$lockPath = '.lock';
if( file_exists( $lockPath ) ) {
  echo 'Process already running; exiting...' . PHP_EOL;
  exit( 1 );
}
else if( $argc == 2 && 'child' == $argv[ 1 ] ) {
  /* child process */

  if( false === ( $lock = fopen( $lockPath,'x' ) ) ) {
    echo 'Unable to acquire lock; exiting...' . PHP_EOL;
    exit( 1 );
  }
  else if( false !== flock( $lock,LOCK_EX ) ) {
    echo 'Process started...' . PHP_EOL;

    $state = 1;

    declare( ticks = 1 );
    pcntl_signal( SIGTSTP,function( $signo ) use ( &$state ) {
      echo 'pcntl_signal SIGTSTP' . PHP_EOL;
      $state = 0;
    } );
    pcntl_signal( SIGCONT,function( $signo ) use ( &$state ) {
      echo 'pcntl_signal SIGCONT' . PHP_EOL;
      $state = 1;
    } );
    pcntl_signal( SIGTERM,function( $signo ) use ( &$state ) {
      echo 'pcntl_signal SIGTERM' . PHP_EOL;
      $state = -1;
    } );

    while( $state !== -1 ) {
      /**
       * It doesn't matter whether I leave the first echo out
       * and/or whether I put either echo's in functions,* Any combination simply works as expected here
       */
      echo 'Server state: ' . $state . PHP_EOL;
      if( $state !== 0 ) {
        echo 'Server tick.' . PHP_EOL;
      }
      usleep( 1000000 );
    }

    flock( $lock,LOCK_UN ) && fclose( $lock ) && unlink( $lockPath );
    echo 'Process ended; unlocked,closed and deleted lock file; exiting...' . PHP_EOL;
    exit( 0 );
  }
}
else {
  /* parent process */

  function consoleRead() {
    $fd = STDIN;
    $read = array( $fd );
    $write = array();
    $except = array();

    $result = stream_select( $read,$write,$except,0 );
    if( $result === false ) {
      throw new RuntimeException( 'stream_select() failed' );
    }
    if( $result === 0 ) {
      return false;
    }

    return stream_get_line( $fd,1024,PHP_EOL );
  }

  $decriptors = array(
    0 => STDIN,1 => STDOUT,2 => STDERR
  );
  $childProcess = proc_open( sprintf( 'exec %s child',__FILE__ ),$decriptors,$pipes );

  while( 1 ) {

    $childStatus = proc_get_status( $childProcess );
    $childPid    = $childStatus[ 'pid' ];
    if( false !== ( $command = consoleRead() ) ) {
      switch( $command ) {
        case 'status':
          var_export( $childStatus );
        break;
        case 'run':
        case 'start':
          // nothing?
        break;
        case 'pause':
        case 'suspend':
          // SIGTSTP
          if( false !== $childPid ) {
            posix_kill( $childPid,SIGTSTP );
          }
        break;
        case 'resume':
        case 'continue':
          // SIGCONT
          if( false !== $childPid ) {
            posix_kill( $childPid,SIGCONT );
          }
        break;
        case 'halt':
        case 'quit':
        case 'stop':
          // SIGTERM
          if( false !== $childPid ) {
            posix_kill( $childPid,SIGTERM );
          }
        break;
      }
    }
    usleep( 1000000 );
  }

  exit( 0 );
}

当您在控制台中运行示例(上方和下方)时,请输入pause< enter>然后恢复< enter>.预期的行为是,在恢复之后,您将再次看到(除此之外)此流:

Server tick.
Server tick.
Server tick.

/编辑

这是我使用的:

控制台和服务器都是我的抽象LoopedProcess类的实例:

abstract class LoopedProcess
{

  const STOPPED = -1;
  const PAUSED  =  0;
  const RUNNING =  1;

  private $state    = self::STOPPED;
  private $throttle = 50;

  final protected function getState() {
    return $this->state;
  }

  final public function isStopped() {
    return self::STOPPED === $this->getState();
  }

  final public function isPaused() {
    return self::PAUSED === $this->getState();
  }

  final public function isRunning() {
    return self::RUNNING === $this->getState();
  }

  protected function onBeforeRun() {}

  protected function onRun() {}

  final public function run() {
    if( $this->isStopped() && false !== $this->onBeforeRun() ) {
      $this->state = self::RUNNING;
      $this->onRun();
      $this->loop();
    }
  }

  protected function onBeforePause() {}

  protected function onPause() {}

  final public function pause() {
    if( $this->isRunning() && false !== $this->onBeforePause() ) {
      $this->state = self::PAUSED;
      $this->onPause();
    }
  }

  protected function onBeforeResume() {}

  protected function onResume() {}

  final public function resume() {
    if( $this->isPaused() && false !== $this->onBeforeResume() ) {
      $this->state = self::RUNNING;
      $this->onResume();
    }
  }

  protected function onBeforeStop() {}

  protected function onStop() {}

  final public function stop() {
    if( !$this->isStopped() && false !== $this->onBeforeStop() ) {
      $this->state = self::STOPPED;
      $this->onStop();
    }
  }

  final protected function setThrottle( $throttle ) {
    $this->throttle = (int) $throttle;
  }

  protected function onLoopStart() {}

  protected function onLoopEnd() {}

  final private function loop() {
    while( !$this->isStopped() ) {
      $this->onLoopStart();
      if( !$this->isPaused() ) {
        $this->tick();
      }
      $this->onLoopEnd();
      usleep( $this->throttle );
    }
  }

  abstract protected function tick();
}

这是一个基于LoopedProcess的非常基本的抽象控制台类:

abstract class Console
  extends LoopedProcess
{

  public function __construct() {
    $this->setThrottle( 1000000 ); // 1 sec
  }

  public function consoleRead() {
    $fd = STDIN;
    $read = array( $fd );
    $write = array();
    $except = array();

    $result = stream_select( $read,PHP_EOL );
  }

  public function consoleWrite( $data ) {
    echo "r$datan";
  }
}

以下实际的服务器控制台扩展了上面的抽象控制台类.在ServerConsole :: tick()内部,您会发现它响应从终端输入的命令,并将信号发送到子进程(实际服务器).

class ServerConsole
  extends Console
{

  private $childProcess;
  private $childProcessId;

  public function __construct() {
    declare( ticks = 1 );
    $self = $this;
    pcntl_signal( SIGINT,function( $signo ) use ( $self ) {
      $self->consoleWrite( 'Console received SIGINT' );
      $self->stop();
    } );
    parent::__construct();
  }

  protected function onBeforeRun() {
    $decriptors = array( /*
      0 => STDIN,2 => STDERR
    */ );
    $this->childProcess = proc_open( sprintf( 'exec %s child',$pipes );

    if( !is_resource( $this->childProcess ) ) {
      $this->consoleWrite( 'Unable to create child process; exiting...' );
      return false;
    }
    else {
      $this->consoleWrite( 'Child process created...' );
    }
  }

  protected function onStop() {
    $this->consoleWrite( 'Parent process ended; exiting...' );
    $childPid = proc_get_status( $this->childProcess )[ 'pid' ];
    if( false !== $childPid ) {
      posix_kill( $childPid,SIGTERM );
    }
  }

  protected function tick() {    
    $childStatus = proc_get_status( $this->childProcess );
    $childPid = $childStatus[ 'pid' ];
    if( false !== ( $command = $this->consoleRead() ) ) {
      var_dump( $childPid,$command );
      switch( $command ) {
        case 'run':
        case 'start':
          // nothing,for now
        break;
        case 'pause':
        case 'suspend':
          // SIGTSTP
          if( false !== $childPid ) {
            posix_kill( $childPid,SIGTERM );
          }
        break;
      }
    }
  }
}

这是服务器实现.这是奇怪的行为发生的地方.如果不覆盖LoopedProcess :: onLoopStart()挂钩,一旦暂停,它将不再响应信号.所以,如果我删除钩子,LoopedProcess :: loop()实际上不再重要了.

class Server
  extends LoopedProcess
{

  public function __construct() {
    declare( ticks = 1 );
    $self = $this;

    // install the signal handlers
    pcntl_signal( SIGTSTP,function( $signo ) use ( $self ) {
      echo 'pcntl_signal SIGTSTP' . PHP_EOL;
      $self->pause();
    } );
    pcntl_signal( SIGCONT,function( $signo ) use ( $self ) {
      echo 'pcntl_signal SIGCONT' . PHP_EOL;
      $self->resume();
    } );
    pcntl_signal( SIGTERM,function( $signo ) use ( $self ) {
      echo 'pcntl_signal SIGTERM' . PHP_EOL;
      $self->stop();
    } );
    $this->setThrottle( 2000000 ); // 2 sec
  }

  protected function tick() {
    echo 'Server tick.' . PHP_EOL;
  }

  protected function onBeforePause() {
    echo 'Server pausing.' . PHP_EOL;
  }

  protected function onPause() {
    echo 'Server paused.' . PHP_EOL;
  }

  protected function onBeforeResume() {
    echo 'Server resuming.' . PHP_EOL;
  }

  protected function onResume() {
    echo 'Server resumed.' . PHP_EOL;
  }

  /**
   * if I remove this hook,Server becomes unresponsive
   * to signals,after it has been paused
   */
  protected function onLoopStart() {
    echo 'Server state: ' . ( $this->getState() ) . PHP_EOL;
  }
}

这是将所有内容联系在一起的脚本:

$lockPath = '.lock';
if( file_exists( $lockPath ) ) {
  echo 'Process already running; exiting...' . PHP_EOL;
  exit( 1 );
}
else if( $argc == 2 && 'child' == $argv[ 1 ] ) {
  /* child process */

  if( false === ( $lock = fopen( $lockPath,LOCK_EX ) ) {
    echo 'Process started...' . PHP_EOL;
    $server = new Server();
    $server->run();
    flock( $lock,closed and deleted lock file; exiting...' . PHP_EOL;
    exit( 0 );
  }
}
else {
  /* parent process */

  $console = new ServerConsole();
  $console->run();
  exit( 0 );
}

所以,总结一下:

当服务器暂停并且在loop()内部实际上没有任何重要性因为我没有实现任何输出的钩子,它对新信号没有反应.但是,当实现钩子时,它会按预期响应信号.

这可能会发生什么?

解决方法

我已经通过在PHP的文档网站上按照 this comment1添加对 pcntl_signal_dispatch()内部循环()的调用来实现它,如下所示:

final private function loop() {
  while( !$this->isStopped() ) {
    $this->onLoopStart();
    if( !$this->isPaused() ) {
      $this->tick();
    }
    $this->onLoopEnd();
    pcntl_signal_dispatch(); // adding this worked
    // (I actually need to put it in onLoopEnd() though,this was just a temporary hack)
    usleep( $this->throttle );
  }
}

但是,我的简化示例脚本不需要这样做.所以我仍然有兴趣知道在什么情况下有必要调用pcntl_signal_dispatch()及其背后的原因,如果有人有任何见解.

1)评论目前隐藏在网站的标题后面,因此您可能需要向上滚动一点.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读