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()
 | 
						|
    {
 | 
						|
    }
 | 
						|
} |