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

时间:2015-06-29 11:32:16

标签: php while-loop signals posix

我正在尝试创建可重用的通用cli服务器,我可以从终端会话控制(启动/暂停/恢复/停止)。

到目前为止,我的方法是,我有一个脚本独立地充当控制台(父循环)和服务器(子循环),而不是pcntl_fork() - 但是proc_open() - 可以这么说,就像儿童过程一样。

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

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

这可能会发生什么?

修改

根据评论中的要求,我简化了我的代码示例。但是,正如我已经担心的那样,这个代码工作正常。

也许我在课程中忽略了代码中的某些内容,但我不知道两个例子在例行程序中有何不同 - 对我来说,看起来两个例子都遵循相同的例程。

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

无论如何,这是我之前的例子的简化版本,我已在下面显示:

loop()

在控制台中运行示例(上方和下方)时,请输入$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>。预期的行为是,在恢复之后,您将再次看到(除此之外)此流:

resume<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, $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 ); } public function consoleWrite( $data ) { echo "\r$data\n"; } } 内,您会发现它响应从终端输入的命令,并将信号发送到子进程(实际服务器)。

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, 1 => STDOUT, 2 => STDERR */ ); $this->childProcess = proc_open( sprintf( 'exec %s child', __FILE__ ), $decriptors, $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, 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; } } } } 挂钩,一旦暂停,它将不再响应信号。所以,如果我删除钩子,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, 'x' ) ) ) { echo 'Unable to acquire lock; exiting...' . PHP_EOL; exit( 1 ); } else if( false !== flock( $lock, LOCK_EX ) ) { echo 'Process started...' . PHP_EOL; $server = new Server(); $server->run(); 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 */ $console = new ServerConsole(); $console->run(); exit( 0 ); } 暂停并在Server内有效地执行任何重要操作时,因为我没有实现任何输出的钩子,它对新信号没有反应。但是,当实现钩子 时,它会按预期响应信号。

这可能会发生什么?

1 个答案:

答案 0 :(得分:2)

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

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)评论目前隐藏在网站的标题后面,因此您可能需要向上滚动一点。