/src/Swarm/Manager.php

https://github.com/max-shamaev/swarm · PHP · 482 lines · 190 code · 67 blank · 225 comment · 23 complexity · 221790d17d51649bfac20698b4f53309 MD5 · raw file

  1. <?php
  2. // vim: set ts=4 sw=4 sts=4 et:
  3. /**
  4. * PHP version 5.3.0
  5. *
  6. * @author ____author____
  7. * @copyright ____copyright____
  8. * @license ____license____
  9. * @link https://github.com/max-shamaev/swarm
  10. * @since 1.0.0
  11. */
  12. namespace Swarm;
  13. /**
  14. * Swarm manager
  15. *
  16. * @see ____class_see____
  17. * @since 1.0.0
  18. */
  19. class Manager extends \Swarm\Base\Singleton
  20. {
  21. /**
  22. * Workers planner
  23. *
  24. * @var \Swarm\Planner
  25. * @see ____var_see____
  26. * @since 1.0.0
  27. */
  28. protected $planner;
  29. /**
  30. * Alive state
  31. *
  32. * @var boolean
  33. * @see ____var_see____
  34. * @since 1.0.0
  35. */
  36. protected $alive = true;
  37. /**
  38. * Reload state
  39. *
  40. * @var boolean
  41. * @see ____var_see____
  42. * @since 1.0.0
  43. */
  44. protected $reload = false;
  45. /**
  46. * Workers process info
  47. *
  48. * @var array
  49. * @see ____var_see____
  50. * @since 1.0.0
  51. */
  52. protected $workers = array();
  53. /**
  54. * Managed worker
  55. *
  56. * @var \Swarm\Worker
  57. * @see ____var_see____
  58. * @since 1.0.0
  59. */
  60. protected $managedWorker;
  61. /**
  62. * Manager pid
  63. *
  64. * @var integer
  65. * @see ____var_see____
  66. * @since 1.0.0
  67. */
  68. protected $pid;
  69. /**
  70. * Get manager process id
  71. *
  72. * @return integer
  73. * @see ____func_see____
  74. * @since 1.0.0
  75. */
  76. public function getPid()
  77. {
  78. return $this->pid;
  79. }
  80. /**
  81. * Check state
  82. *
  83. * @return void
  84. * @see ____func_see____
  85. * @since 1.0.0
  86. * @throws \Swarm\BlockException
  87. */
  88. public function checkState()
  89. {
  90. if ($this->managedWorker && $this->managedWorker->getBlocking()) {
  91. pcntl_signal_dispatch();
  92. if (!$this->isAlive()) {
  93. throw new \Swarm\BlockException;
  94. }
  95. }
  96. }
  97. /**
  98. * Constructor
  99. *
  100. * @return void
  101. * @see ____func_see____
  102. * @since 1.0.0
  103. */
  104. protected function __construct()
  105. {
  106. declare(ticks = 1);
  107. register_tick_function(array($this, 'checkState'));
  108. $this->pid = posix_getpid();
  109. $this->assignSignals();
  110. }
  111. /**
  112. * Assign signals
  113. *
  114. * @return void
  115. * @see ____func_see____
  116. * @since 1.0.0
  117. */
  118. protected function assignSignals()
  119. {
  120. pcntl_signal(SIGTERM, array($this, 'processSigTerm'));
  121. pcntl_signal(SIGQUIT, array($this, 'processSigTerm'));
  122. pcntl_signal(SIGTSTP, array($this, 'processSigTerm'));
  123. pcntl_signal(SIGINT, array($this, 'processSigTerm'));
  124. pcntl_signal(SIGHUP, array($this, 'processSigHup'));
  125. }
  126. // {{{ Process name operations
  127. /**
  128. * Renew current process name
  129. *
  130. * @return void
  131. * @see ____func_see____
  132. * @since 1.0.0
  133. */
  134. public function renewProcessName()
  135. {
  136. if (function_exists('setproctitle')) {
  137. setproctitle($this->getProcessName());
  138. }
  139. }
  140. /**
  141. * Get process name
  142. *
  143. * @return string
  144. * @see ____func_see____
  145. * @since 1.0.0
  146. */
  147. protected function getProcessName()
  148. {
  149. return $this->managedWorker
  150. ? $this->managedWorker->getProcessName()
  151. : $this->getManagerProcessName();
  152. }
  153. /**
  154. * Get manager process name
  155. *
  156. * @return string
  157. * @see ____func_see____
  158. * @since 1.0.0
  159. */
  160. protected function getManagerProcessName()
  161. {
  162. return 'swarm manager';
  163. }
  164. // }}}
  165. // {{{ Signals processing
  166. /**
  167. * Process termination signal
  168. *
  169. * @param integre $signal Signal
  170. *
  171. * @return void
  172. * @see ____func_see____
  173. * @since 1.0.0
  174. */
  175. public function processSigTerm($signal)
  176. {
  177. $this->alive = false;
  178. }
  179. /**
  180. * Process hand up signal
  181. *
  182. * @param integre $signal Signal
  183. *
  184. * @return void
  185. * @see ____func_see____
  186. * @since 1.0.0
  187. */
  188. public function processSigHup($signal)
  189. {
  190. $this->reload = true;
  191. }
  192. // }}}
  193. // {{{ Manage workes
  194. /**
  195. * Run manager
  196. *
  197. * @return boolean Manager or not
  198. * @see ____func_see____
  199. * @since 1.0.0
  200. * @throws \Swarm\Exception
  201. */
  202. public function run()
  203. {
  204. if (!$this->isReady()) {
  205. throw \Swarm\Exception('Can not run manager - it is not ready to run');
  206. }
  207. $this->renewProcessName();
  208. $exitWorker = false;
  209. while ($this->isAlive()) {
  210. if (!$this->checkWorkers()) {
  211. $exitWorker = true;
  212. break;
  213. }
  214. $this->wait();
  215. pcntl_signal_dispatch();
  216. }
  217. if (!$exitWorker) {
  218. $this->killWorkers();
  219. $status = 0;
  220. pcntl_wait($status);
  221. }
  222. return !$exitWorker;
  223. }
  224. /**
  225. * Check alive state
  226. *
  227. * @return boolean
  228. * @see ____func_see____
  229. * @since 1.0.0
  230. */
  231. public function isAlive()
  232. {
  233. return $this->alive;
  234. }
  235. /**
  236. * Check manager state
  237. *
  238. * @return boolean
  239. * @see ____func_see____
  240. * @since 1.0.0
  241. */
  242. protected function isReady()
  243. {
  244. return isset($this->planner);
  245. }
  246. /**
  247. * Check and (re)start workers
  248. *
  249. * @return boolean
  250. * @see ____func_see____
  251. * @since 1.0.0
  252. */
  253. protected function checkWorkers()
  254. {
  255. $result = true;
  256. if ($this->reload) {
  257. $this->reload = false;
  258. $this->killWorkers();
  259. }
  260. $checked = array();
  261. $workers = $this->planner->getWorkers();
  262. foreach ($workers as $info) {
  263. $found = false;
  264. foreach ($this->workers as $index => $worker) {
  265. if (!in_array($index, $checked) && $worker['class'] == $info['class']) {
  266. if ($this->checkWorker($worker)) {
  267. $found = $index;
  268. break;
  269. } else {
  270. unset($this->workers[$index]);
  271. }
  272. }
  273. }
  274. if (false !== $found) {
  275. $checked[] = $found;
  276. } else {
  277. $result = $this->startWorker($info);
  278. if (is_array($result)) {
  279. // Create worker
  280. $this->workers[] = $result;
  281. end($this->workers);
  282. $checked[] = key($this->workers);
  283. } elseif ($result) {
  284. // Exit worker
  285. $result = false;
  286. break;
  287. }
  288. }
  289. }
  290. if ($result) {
  291. // Remove obsolete workers
  292. foreach ($this->workers as $index => $worker) {
  293. if (!in_array($index, $checked)) {
  294. $this->killWorker($index);
  295. }
  296. }
  297. }
  298. return $result;
  299. }
  300. /**
  301. * Start worker
  302. *
  303. * @param array $info Worker info
  304. *
  305. * @return aray|boolean
  306. * @see ____func_see____
  307. * @since 1.0.0
  308. */
  309. protected function startWorker(array $info)
  310. {
  311. $pid = pcntl_fork();
  312. if ($pid) {
  313. $result = array('pid' => $pid) + $info;
  314. } elseif (posix_setsid() == -1) {
  315. $result = false;
  316. } else {
  317. try {
  318. $this->runWorker($info);
  319. } catch (\Swarm\BlockException $e) {
  320. }
  321. $result = true;
  322. }
  323. return $result;
  324. }
  325. /**
  326. * Start worker
  327. *
  328. * @param array $info Worker info
  329. *
  330. * @return void
  331. * @see ____func_see____
  332. * @since 1.0.0
  333. */
  334. protected function runWorker(array $info)
  335. {
  336. $this->managedWorker = new $info['class']($this);
  337. $this->renewProcessName();
  338. $this->managedWorker->run(isset($info['arguments']) ? $info['arguments'] : null);
  339. die(0);
  340. }
  341. /**
  342. * Check worker
  343. *
  344. * @param array $worker Worker info
  345. *
  346. * @return boolean
  347. * @see ____func_see____
  348. * @since 1.0.0
  349. */
  350. protected function checkWorker(array $worker)
  351. {
  352. $status = 0;
  353. pcntl_waitpid($worker['pid'], $status, WNOHANG);
  354. return posix_getsid($worker['pid']);
  355. }
  356. /**
  357. * Kill all workers
  358. *
  359. * @return void
  360. * @see ____func_see____
  361. * @since 1.0.0
  362. */
  363. protected function killWorkers()
  364. {
  365. foreach ($this->workers as $index => $pid) {
  366. $this->killWorker($index);
  367. }
  368. }
  369. /**
  370. * Kill worker by workers list index
  371. *
  372. * @param integer $index Index
  373. *
  374. * @return void
  375. * @see ____func_see____
  376. * @since 1.0.0
  377. */
  378. protected function killWorker($index)
  379. {
  380. if (isset($this->workers[$index])) {
  381. posix_kill($this->workers[$index]['pid'], SIGTERM);
  382. $status = 0;
  383. pcntl_waitpid($this->workers[$index]['pid'], $status);
  384. unset($this->workers[$index]);
  385. }
  386. }
  387. /**
  388. * Wait
  389. *
  390. * @return void
  391. * @see ____func_see____
  392. * @since 1.0.0
  393. */
  394. protected function wait()
  395. {
  396. sleep(1);
  397. }
  398. // }}}
  399. // {{{ Settings
  400. /**
  401. * Set planner
  402. *
  403. * @param \Swarm\Planner $planner Planner
  404. *
  405. * @return \Swarm\Manager
  406. * @see ____func_see____
  407. * @since 1.0.0
  408. */
  409. public function setPlanner(\Swarm\Planner $planner)
  410. {
  411. $this->planner = $planner;
  412. return $this;
  413. }
  414. // }}}
  415. }