php – 当无限循环无效时,脚本对信号没有响应
我正在尝试创建一个可重用的通用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)评论目前隐藏在网站的标题后面,因此您可能需要向上滚动一点. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |