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