Process.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Jenner
  5. * Date: 2015/8/12
  6. * Time: 15:25
  7. */
  8. namespace Jenner\SimpleFork;
  9. class Process
  10. {
  11. /**
  12. * @var Runnable|callable
  13. */
  14. protected $runnable;
  15. /**
  16. * @var int
  17. */
  18. protected $pid = 0;
  19. /**
  20. * @var string custom process name
  21. */
  22. protected $name = null;
  23. /**
  24. * @var bool if the process is started
  25. */
  26. protected $started = false;
  27. /**
  28. * @var bool
  29. */
  30. protected $running = false;
  31. /**
  32. * @var int the signal which made the process terminate
  33. */
  34. protected $term_signal = null;
  35. /**
  36. * @var int the signal which made the process stop
  37. */
  38. protected $stop_signal = null;
  39. /**
  40. * @var int error code
  41. */
  42. protected $errno = null;
  43. /**
  44. * @var string error message
  45. */
  46. protected $errmsg = null;
  47. /**
  48. * @var bool
  49. */
  50. protected $if_signal = false;
  51. /**
  52. * @var array
  53. */
  54. protected $callbacks = array();
  55. /**
  56. * @var array signal handlers
  57. */
  58. protected $signal_handlers = array();
  59. /**
  60. * @param string $execution it can be a Runnable object, callback function or null
  61. * @param null $name process name,you can manager the process by it's name.
  62. */
  63. public function __construct($execution = null, $name = null)
  64. {
  65. if (!is_null($execution) && $execution instanceof Runnable) {
  66. $this->runnable = $execution;
  67. } elseif (!is_null($execution) && is_callable($execution)) {
  68. $this->runnable = $execution;
  69. } elseif (!is_null($execution)) {
  70. throw new \InvalidArgumentException('param execution is not a object of Runnable or callable');
  71. } else {
  72. Utils::checkOverwriteRunMethod(get_class($this));
  73. }
  74. if (!is_null($name)) {
  75. $this->name = $name;
  76. }
  77. $this->initStatus();
  78. }
  79. /**
  80. * init process status
  81. */
  82. protected function initStatus()
  83. {
  84. $this->pid = null;
  85. $this->running = null;
  86. $this->term_signal = null;
  87. $this->stop_signal = null;
  88. $this->errno = null;
  89. $this->errmsg = null;
  90. }
  91. /**
  92. * get pid
  93. *
  94. * @return int
  95. */
  96. public function getPid()
  97. {
  98. return $this->pid;
  99. }
  100. /**
  101. * get or set name
  102. *
  103. * @param string|null $name
  104. * @return mixed
  105. */
  106. public function name($name = null)
  107. {
  108. if (!is_null($name)) {
  109. $this->name = $name;
  110. } else {
  111. return $this->name;
  112. }
  113. }
  114. /**
  115. * if the process is stopped
  116. *
  117. * @return bool
  118. */
  119. public function isStopped()
  120. {
  121. if (is_null($this->errno)) {
  122. return false;
  123. }
  124. return true;
  125. }
  126. /**
  127. * if the process is started
  128. *
  129. * @return bool
  130. */
  131. public function isStarted()
  132. {
  133. return $this->started;
  134. }
  135. /**
  136. * get pcntl errno
  137. *
  138. * @return int
  139. */
  140. public function errno()
  141. {
  142. return $this->errno;
  143. }
  144. /**
  145. * get pcntl errmsg
  146. *
  147. * @return string
  148. */
  149. public function errmsg()
  150. {
  151. return $this->errmsg;
  152. }
  153. public function ifSignal()
  154. {
  155. return $this->if_signal;
  156. }
  157. /**
  158. * start the sub process
  159. * and run the callback
  160. *
  161. * @return string pid
  162. */
  163. public function start()
  164. {
  165. if (!empty($this->pid) && $this->isRunning()) {
  166. throw new \LogicException("the process is already running");
  167. }
  168. $callback = $this->getCallable();
  169. $pid = pcntl_fork();
  170. if ($pid < 0) {
  171. throw new \RuntimeException("fork error");
  172. } elseif ($pid > 0) {
  173. $this->pid = $pid;
  174. $this->running = true;
  175. $this->started = true;
  176. } else {
  177. $this->pid = getmypid();
  178. $this->signal();
  179. foreach ($this->signal_handlers as $signal => $handler) {
  180. pcntl_signal($signal, $handler);
  181. }
  182. call_user_func($callback);
  183. exit(0);
  184. }
  185. }
  186. /**
  187. * if the process is running
  188. *
  189. * @return bool
  190. */
  191. public function isRunning()
  192. {
  193. $this->updateStatus();
  194. return $this->running;
  195. }
  196. /**
  197. * update the process status
  198. *
  199. * @param bool $block
  200. */
  201. protected function updateStatus($block = false)
  202. {
  203. if ($this->running !== true) {
  204. return;
  205. }
  206. if ($block) {
  207. $res = pcntl_waitpid($this->pid, $status);
  208. } else {
  209. $res = pcntl_waitpid($this->pid, $status, WNOHANG | WUNTRACED);
  210. }
  211. if ($res === -1) {
  212. throw new \RuntimeException('pcntl_waitpid failed. the process maybe available');
  213. } elseif ($res === 0) {
  214. $this->running = true;
  215. } else {
  216. if (pcntl_wifsignaled($status)) {
  217. $this->term_signal = pcntl_wtermsig($status);
  218. }
  219. if (pcntl_wifstopped($status)) {
  220. $this->stop_signal = pcntl_wstopsig($status);
  221. }
  222. if (pcntl_wifexited($status)) {
  223. $this->errno = pcntl_wexitstatus($status);
  224. $this->errmsg = pcntl_strerror($this->errno);
  225. } else {
  226. $this->errno = pcntl_get_last_error();
  227. $this->errmsg = pcntl_strerror($this->errno);
  228. }
  229. if (pcntl_wifsignaled($status)) {
  230. $this->if_signal = true;
  231. } else {
  232. $this->if_signal = false;
  233. }
  234. $this->running = false;
  235. }
  236. }
  237. /**
  238. * get sub process callback
  239. *
  240. * @return array|callable|null
  241. */
  242. protected function getCallable()
  243. {
  244. $callback = null;
  245. if (is_object($this->runnable) && $this->runnable instanceof Runnable) {
  246. $callback = array($this->runnable, 'run');
  247. } elseif (is_callable($this->runnable)) {
  248. $callback = $this->runnable;
  249. } else {
  250. $callback = array($this, 'run');
  251. }
  252. return $callback;
  253. }
  254. /**
  255. * register signal SIGTERM handler,
  256. * when the parent process call shutdown and use the default signal,
  257. * this handler will be triggered
  258. */
  259. protected function signal()
  260. {
  261. pcntl_signal(SIGTERM, function () {
  262. exit(0);
  263. });
  264. }
  265. /**
  266. * kill self
  267. *
  268. * @param bool|true $block
  269. * @param int $signal
  270. */
  271. public function shutdown($block = true, $signal = SIGTERM)
  272. {
  273. if (empty($this->pid)) {
  274. throw new \LogicException('the process pid is null, so maybe the process is not started');
  275. }
  276. if (!$this->isRunning()) {
  277. throw new \LogicException("the process is not running");
  278. }
  279. if (!posix_kill($this->pid, $signal)) {
  280. throw new \RuntimeException("kill son process failed");
  281. }
  282. $this->updateStatus($block);
  283. }
  284. /**
  285. * waiting for the sub process exit
  286. *
  287. * @param bool|true $block if block the process
  288. * @param int $sleep default 0.1s check sub process status
  289. * every $sleep milliseconds.
  290. */
  291. public function wait($block = true, $sleep = 100000)
  292. {
  293. while (true) {
  294. if ($this->isRunning() === false) {
  295. return;
  296. }
  297. if (!$block) {
  298. break;
  299. }
  300. usleep($sleep);
  301. }
  302. }
  303. /**
  304. * register sub process signal handler,
  305. * when the sub process start, the handlers will be registered
  306. *
  307. * @param $signal
  308. * @param callable $handler
  309. */
  310. public function registerSignalHandler($signal, callable $handler)
  311. {
  312. $this->signal_handlers[$signal] = $handler;
  313. }
  314. /**
  315. * after php-5.3.0, we can call pcntl_singal_dispatch to call signal handlers for pending signals
  316. * which can save cpu resources than using declare(tick=n)
  317. *
  318. * @return bool
  319. */
  320. public function dispatchSignal()
  321. {
  322. return pcntl_signal_dispatch();
  323. }
  324. /**
  325. * you should overwrite this function
  326. * if you do not use the Runnable or callback.
  327. */
  328. public function run()
  329. {
  330. }
  331. }