373 lines
8.2 KiB
PHP
373 lines
8.2 KiB
PHP
<?php
|
|
/**
|
|
* Created by PhpStorm.
|
|
* User: Jenner
|
|
* Date: 2015/8/12
|
|
* Time: 15:25
|
|
*/
|
|
|
|
namespace Jenner\SimpleFork;
|
|
|
|
class Process
|
|
{
|
|
/**
|
|
* @var Runnable|callable
|
|
*/
|
|
protected $runnable;
|
|
|
|
/**
|
|
* @var int
|
|
*/
|
|
protected $pid = 0;
|
|
|
|
/**
|
|
* @var string custom process name
|
|
*/
|
|
protected $name = null;
|
|
|
|
/**
|
|
* @var bool if the process is started
|
|
*/
|
|
protected $started = false;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $running = false;
|
|
|
|
/**
|
|
* @var int the signal which made the process terminate
|
|
*/
|
|
protected $term_signal = null;
|
|
|
|
/**
|
|
* @var int the signal which made the process stop
|
|
*/
|
|
protected $stop_signal = null;
|
|
|
|
/**
|
|
* @var int error code
|
|
*/
|
|
protected $errno = null;
|
|
|
|
/**
|
|
* @var string error message
|
|
*/
|
|
protected $errmsg = null;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
protected $if_signal = false;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $callbacks = array();
|
|
|
|
/**
|
|
* @var array signal handlers
|
|
*/
|
|
protected $signal_handlers = array();
|
|
|
|
|
|
/**
|
|
* @param string $execution it can be a Runnable object, callback function or null
|
|
* @param null $name process name,you can manager the process by it's name.
|
|
*/
|
|
public function __construct($execution = null, $name = null)
|
|
{
|
|
if (!is_null($execution) && $execution instanceof Runnable) {
|
|
$this->runnable = $execution;
|
|
} elseif (!is_null($execution) && is_callable($execution)) {
|
|
$this->runnable = $execution;
|
|
} elseif (!is_null($execution)) {
|
|
throw new \InvalidArgumentException('param execution is not a object of Runnable or callable');
|
|
} else {
|
|
Utils::checkOverwriteRunMethod(get_class($this));
|
|
}
|
|
if (!is_null($name)) {
|
|
$this->name = $name;
|
|
}
|
|
|
|
$this->initStatus();
|
|
}
|
|
|
|
/**
|
|
* init process status
|
|
*/
|
|
protected function initStatus()
|
|
{
|
|
$this->pid = null;
|
|
$this->running = null;
|
|
$this->term_signal = null;
|
|
$this->stop_signal = null;
|
|
$this->errno = null;
|
|
$this->errmsg = null;
|
|
}
|
|
|
|
/**
|
|
* get pid
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getPid()
|
|
{
|
|
return $this->pid;
|
|
}
|
|
|
|
/**
|
|
* get or set name
|
|
*
|
|
* @param string|null $name
|
|
* @return mixed
|
|
*/
|
|
public function name($name = null)
|
|
{
|
|
if (!is_null($name)) {
|
|
$this->name = $name;
|
|
} else {
|
|
return $this->name;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* if the process is stopped
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isStopped()
|
|
{
|
|
if (is_null($this->errno)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* if the process is started
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isStarted()
|
|
{
|
|
return $this->started;
|
|
}
|
|
|
|
/**
|
|
* get pcntl errno
|
|
*
|
|
* @return int
|
|
*/
|
|
public function errno()
|
|
{
|
|
return $this->errno;
|
|
}
|
|
|
|
/**
|
|
* get pcntl errmsg
|
|
*
|
|
* @return string
|
|
*/
|
|
public function errmsg()
|
|
{
|
|
return $this->errmsg;
|
|
}
|
|
|
|
public function ifSignal()
|
|
{
|
|
return $this->if_signal;
|
|
}
|
|
|
|
/**
|
|
* start the sub process
|
|
* and run the callback
|
|
*
|
|
* @return string pid
|
|
*/
|
|
public function start()
|
|
{
|
|
if (!empty($this->pid) && $this->isRunning()) {
|
|
throw new \LogicException("the process is already running");
|
|
}
|
|
|
|
$callback = $this->getCallable();
|
|
|
|
$pid = pcntl_fork();
|
|
if ($pid < 0) {
|
|
throw new \RuntimeException("fork error");
|
|
} elseif ($pid > 0) {
|
|
$this->pid = $pid;
|
|
$this->running = true;
|
|
$this->started = true;
|
|
} else {
|
|
$this->pid = getmypid();
|
|
$this->signal();
|
|
foreach ($this->signal_handlers as $signal => $handler) {
|
|
pcntl_signal($signal, $handler);
|
|
}
|
|
call_user_func($callback);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* if the process is running
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isRunning()
|
|
{
|
|
$this->updateStatus();
|
|
return $this->running;
|
|
}
|
|
|
|
/**
|
|
* update the process status
|
|
*
|
|
* @param bool $block
|
|
*/
|
|
protected function updateStatus($block = false)
|
|
{
|
|
if ($this->running !== true) {
|
|
return;
|
|
}
|
|
|
|
if ($block) {
|
|
$res = pcntl_waitpid($this->pid, $status);
|
|
} else {
|
|
$res = pcntl_waitpid($this->pid, $status, WNOHANG | WUNTRACED);
|
|
}
|
|
|
|
if ($res === -1) {
|
|
throw new \RuntimeException('pcntl_waitpid failed. the process maybe available');
|
|
} elseif ($res === 0) {
|
|
$this->running = true;
|
|
} else {
|
|
if (pcntl_wifsignaled($status)) {
|
|
$this->term_signal = pcntl_wtermsig($status);
|
|
}
|
|
if (pcntl_wifstopped($status)) {
|
|
$this->stop_signal = pcntl_wstopsig($status);
|
|
}
|
|
if (pcntl_wifexited($status)) {
|
|
$this->errno = pcntl_wexitstatus($status);
|
|
$this->errmsg = pcntl_strerror($this->errno);
|
|
} else {
|
|
$this->errno = pcntl_get_last_error();
|
|
$this->errmsg = pcntl_strerror($this->errno);
|
|
}
|
|
if (pcntl_wifsignaled($status)) {
|
|
$this->if_signal = true;
|
|
} else {
|
|
$this->if_signal = false;
|
|
}
|
|
|
|
$this->running = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get sub process callback
|
|
*
|
|
* @return array|callable|null
|
|
*/
|
|
protected function getCallable()
|
|
{
|
|
$callback = null;
|
|
if (is_object($this->runnable) && $this->runnable instanceof Runnable) {
|
|
$callback = array($this->runnable, 'run');
|
|
} elseif (is_callable($this->runnable)) {
|
|
$callback = $this->runnable;
|
|
} else {
|
|
$callback = array($this, 'run');
|
|
}
|
|
|
|
return $callback;
|
|
}
|
|
|
|
/**
|
|
* register signal SIGTERM handler,
|
|
* when the parent process call shutdown and use the default signal,
|
|
* this handler will be triggered
|
|
*/
|
|
protected function signal()
|
|
{
|
|
pcntl_signal(SIGTERM, function () {
|
|
exit(0);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* kill self
|
|
*
|
|
* @param bool|true $block
|
|
* @param int $signal
|
|
*/
|
|
public function shutdown($block = true, $signal = SIGTERM)
|
|
{
|
|
if (empty($this->pid)) {
|
|
throw new \LogicException('the process pid is null, so maybe the process is not started');
|
|
}
|
|
if (!$this->isRunning()) {
|
|
throw new \LogicException("the process is not running");
|
|
}
|
|
if (!posix_kill($this->pid, $signal)) {
|
|
throw new \RuntimeException("kill son process failed");
|
|
}
|
|
|
|
$this->updateStatus($block);
|
|
}
|
|
|
|
/**
|
|
* waiting for the sub process exit
|
|
*
|
|
* @param bool|true $block if block the process
|
|
* @param int $sleep default 0.1s check sub process status
|
|
* every $sleep milliseconds.
|
|
*/
|
|
public function wait($block = true, $sleep = 100000)
|
|
{
|
|
while (true) {
|
|
if ($this->isRunning() === false) {
|
|
return;
|
|
}
|
|
if (!$block) {
|
|
break;
|
|
}
|
|
usleep($sleep);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* register sub process signal handler,
|
|
* when the sub process start, the handlers will be registered
|
|
*
|
|
* @param $signal
|
|
* @param callable $handler
|
|
*/
|
|
public function registerSignalHandler($signal, callable $handler)
|
|
{
|
|
$this->signal_handlers[$signal] = $handler;
|
|
}
|
|
|
|
/**
|
|
* after php-5.3.0, we can call pcntl_singal_dispatch to call signal handlers for pending signals
|
|
* which can save cpu resources than using declare(tick=n)
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dispatchSignal()
|
|
{
|
|
return pcntl_signal_dispatch();
|
|
}
|
|
|
|
/**
|
|
* you should overwrite this function
|
|
* if you do not use the Runnable or callback.
|
|
*/
|
|
public function run()
|
|
{
|
|
}
|
|
} |