PageRenderTime 50ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/library/ZendX/Console/Process/Unix.php

https://bitbucket.org/philkershaw/zend-framework-1.11-acl-implementation
PHP | 658 lines | 277 code | 91 blank | 290 comment | 42 complexity | cabf8b668b5062e8acb348f353170ac6 MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category ZendX
  16. * @package ZendX_Console
  17. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id: Unix.php 20165 2010-01-09 18:57:56Z bkarwin $
  20. */
  21. /**
  22. * ZendX_Console_Process_Unix allows you to spawn a class as a separated process
  23. *
  24. * @category ZendX
  25. * @package ZendX_Console
  26. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. abstract class ZendX_Console_Process_Unix
  30. {
  31. /**
  32. * Void method
  33. */
  34. const VOID_METHOD = 'void_method';
  35. /**
  36. * Return method
  37. */
  38. const RETURN_METHOD = 'void_method';
  39. /**
  40. * Unique thread name
  41. *
  42. * @var string
  43. */
  44. private $_name;
  45. /**
  46. * PID of the child process
  47. *
  48. * @var integer
  49. */
  50. private $_pid = null;
  51. /**
  52. * UID of the child process owner
  53. *
  54. * @var integer
  55. */
  56. private $_puid = null;
  57. /**
  58. * GUID of the child process owner
  59. *
  60. * @var integer
  61. */
  62. private $_guid = null;
  63. /**
  64. * Whether the process is yet forked or not
  65. *
  66. * @var boolean
  67. */
  68. private $_isRunning = false;
  69. /**
  70. * Wether we are into child process or not
  71. *
  72. * @var boolean
  73. */
  74. private $_isChild = false;
  75. /**
  76. * A data structure to hold data for Inter Process Communications
  77. *
  78. * @var array
  79. */
  80. private $_internalIpcData = array();
  81. /**
  82. * Key to access to Shared Memory Area.
  83. *
  84. * @var integer
  85. */
  86. private $_internalIpcKey;
  87. /**
  88. * Key to access to Sync Semaphore.
  89. *
  90. * @var integer
  91. */
  92. private $_internalSemKey;
  93. /**
  94. * Is Shared Memory Area OK? If not, the start() method will block.
  95. * Otherwise we'll have a running child without any communication channel.
  96. *
  97. * @var boolean
  98. */
  99. private $_ipcIsOkay;
  100. /**
  101. * Filename of the IPC segment file
  102. *
  103. * @var string
  104. */
  105. private $_ipcSegFile;
  106. /**
  107. * Filename of the semaphor file
  108. *
  109. * @var string
  110. */
  111. private $_ipcSemFile;
  112. /**
  113. * Constructor method
  114. *
  115. * Allocates a new pseudo-thread object. Optionally, set a PUID, a GUID and
  116. * a UMASK for the child process. This also initialize Shared Memory
  117. * Segments for process communications.
  118. *
  119. * @param integer $puid
  120. * @param integer $guid
  121. * @param integer $umask
  122. * @throws ZendX_Console_Process_Exception When running on windows
  123. * @throws ZendX_Console_Process_Exception When running in web enviroment
  124. * @throws ZendX_Console_Process_Exception When shmop_* functions don't exist
  125. * @throws ZendX_Console_Process_Exception When pcntl_* functions don't exist
  126. * @throws ZendX_Console_Process_Exception When posix_* functions don't exist
  127. */
  128. public function __construct($puid = null, $guid = null, $umask = null)
  129. {
  130. if (substr(PHP_OS, 0, 3) === 'WIN') {
  131. require_once 'ZendX/Console/Process/Exception.php';
  132. throw new ZendX_Console_Process_Exception('Cannot run on windows');
  133. } else if (!in_array(substr(PHP_SAPI, 0, 3), array('cli', 'cgi'))) {
  134. require_once 'ZendX/Console/Process/Exception.php';
  135. throw new ZendX_Console_Process_Exception('Can only run on CLI or CGI enviroment');
  136. } else if (!function_exists('shmop_open')) {
  137. require_once 'ZendX/Console/Process/Exception.php';
  138. throw new ZendX_Console_Process_Exception('shmop_* functions are required');
  139. } else if (!function_exists('pcntl_fork')) {
  140. require_once 'ZendX/Console/Process/Exception.php';
  141. throw new ZendX_Console_Process_Exception('pcntl_* functions are required');
  142. } else if (!function_exists('posix_kill')) {
  143. require_once 'ZendX/Console/Process/Exception.php';
  144. throw new ZendX_Console_Process_Exception('posix_* functions are required');
  145. }
  146. $this->_isRunning = false;
  147. $this->_name = md5(uniqid(rand()));
  148. $this->_guid = $guid;
  149. $this->_puid = $puid;
  150. if ($umask !== null) {
  151. umask($umask);
  152. }
  153. // Try to create the shared memory segment. The variable
  154. // $this->_ipcIsOkay contains the return code of this operation and must
  155. // be checked before forking
  156. if ($this->_createIpcSegment() && $this->_createIpcSemaphore()) {
  157. $this->_ipcIsOkay = true;
  158. } else {
  159. $this->_ipcIsOkay = false;
  160. }
  161. }
  162. /**
  163. * Stop the child on destruction
  164. */
  165. public function __destruct()
  166. {
  167. if ($this->isRunning()) {
  168. $this->stop();
  169. }
  170. }
  171. /**
  172. * Causes this pseudo-thread to begin parallel execution.
  173. *
  174. * This method first checks of all the Shared Memory Segment. If okay, it
  175. * forks the child process, attaches signal handler and returns immediatly.
  176. * The status is set to running, and a PID is assigned. The result is that
  177. * two pseudo-threads are running concurrently: the current thread (which
  178. * returns from the call to the start() method) and the other thread (which
  179. * executes its run() method).
  180. *
  181. * @throws ZendX_Console_Process_Exception When SHM segments can't be created
  182. * @throws ZendX_Console_Process_Exception When process forking fails
  183. * @return void
  184. */
  185. public function start()
  186. {
  187. if (!$this->_ipcIsOkay) {
  188. require_once 'ZendX/Console/Process/Exception.php';
  189. throw new ZendX_Console_Process_Exception('Unable to create SHM segments for process communications');
  190. }
  191. // @see http://www.php.net/manual/en/function.pcntl-fork.php#41150
  192. @ob_end_flush();
  193. pcntl_signal(SIGCHLD, SIG_IGN);
  194. $pid = @pcntl_fork();
  195. if ($pid === -1) {
  196. require_once 'ZendX/Console/Process/Exception.php';
  197. throw new ZendX_Console_Process_Exception('Forking process failed');
  198. } else if ($pid === 0) {
  199. // This is the child
  200. $this->_isChild = true;
  201. // Sleep a second to avoid problems
  202. sleep(1);
  203. // Install the signal handler
  204. pcntl_signal(SIGUSR1, array($this, '_sigHandler'));
  205. // If requested, change process identity
  206. if ($this->_guid !== null) {
  207. posix_setgid($this->_guid);
  208. }
  209. if ($this->_puid !== null) {
  210. posix_setuid($this->_puid);
  211. }
  212. // Run the child
  213. try {
  214. $this->_run();
  215. } catch (Exception $e) {
  216. // We have to catch any exceptions and clean up the process,
  217. // else we will have a memory leak.
  218. }
  219. // Destroy the child after _run() execution. Required to avoid
  220. // unuseful child processes after execution
  221. exit(0);
  222. } else {
  223. // Else this is the parent
  224. $this->_isChild = false;
  225. $this->_isRunning = true;
  226. $this->_pid = $pid;
  227. }
  228. }
  229. /**
  230. * Causes the current thread to die.
  231. *
  232. * The relative process is killed and disappears immediately from the
  233. * processes list.
  234. *
  235. * @return boolean
  236. */
  237. public function stop()
  238. {
  239. $success = false;
  240. if ($this->_pid > 0) {
  241. $status = 0;
  242. posix_kill($this->_pid, 9);
  243. pcntl_waitpid($this->_pid, $status, WNOHANG);
  244. $success = pcntl_wifexited($status);
  245. $this->_cleanProcessContext();
  246. }
  247. return $success;
  248. }
  249. /**
  250. * Test if the pseudo-thread is already started.
  251. *
  252. * @return boolean
  253. */
  254. public function isRunning()
  255. {
  256. return $this->_isRunning;
  257. }
  258. /**
  259. * Set a variable into the shared memory segment, so that it can accessed
  260. * both from the parent and from the child process. Variable names
  261. * beginning with underlines are only permitted to interal functions.
  262. *
  263. * @param string $name
  264. * @param mixed $value
  265. * @throws ZendX_Console_Process_Exception When an invalid variable name is supplied
  266. * @return void
  267. */
  268. public function setVariable($name, $value)
  269. {
  270. if ($name[0] === '_') {
  271. require_once 'ZendX/Console/Process/Exception.php';
  272. throw new ZendX_Console_Process_Exception('Only internal functions may use underline (_) as variable prefix');
  273. }
  274. $this->_writeVariable($name, $value);
  275. }
  276. /**
  277. * Get a variable from the shared memory segment. Returns NULL if the
  278. * variable doesn't exist.
  279. *
  280. * @param string $name
  281. * @return mixed
  282. */
  283. public function getVariable($name)
  284. {
  285. $this->_readFromIpcSegment();
  286. if (isset($this->_internalIpcData[$name])) {
  287. return $this->_internalIpcData[$name];
  288. } else {
  289. return null;
  290. }
  291. }
  292. /**
  293. * Read the time elapsed since the last child setAlive() call.
  294. *
  295. * This method is useful because often we have a pseudo-thread pool and we
  296. * need to know each pseudo-thread status. If the child executes the
  297. * setAlive() method, the parent with getLastAlive() can know that child is
  298. * alive.
  299. *
  300. * @return integer
  301. */
  302. public function getLastAlive()
  303. {
  304. $pingTime = $this->getVariable('_pingTime');
  305. return ($pingTime === null ? 0 : (time() - $pingTime));
  306. }
  307. /**
  308. * Returns the PID of the current pseudo-thread.
  309. *
  310. * @return integer
  311. */
  312. public function getPid()
  313. {
  314. return $this->_pid;
  315. }
  316. /**
  317. * Set a pseudo-thread property that can be read from parent process
  318. * in order to know the child activity.
  319. *
  320. * Practical usage requires that child process calls this method at regular
  321. * time intervals; parent will use the getLastAlive() method to know
  322. * the elapsed time since the last pseudo-thread life signals...
  323. *
  324. * @return void
  325. */
  326. protected function _setAlive()
  327. {
  328. $this->_writeVariable('_pingTime', time());
  329. }
  330. /**
  331. * This is called from within the parent; all the communication stuff
  332. * is done here.
  333. *
  334. * @param string $methodName
  335. * @param array $argList
  336. * @param string $type
  337. * @return mixed
  338. */
  339. protected function _callCallbackMethod($methodName, array $argList = array(), $type = self::VOID_METHOD)
  340. {
  341. // This is the parent, so we really cannot execute the method. Check
  342. // arguments passed to the method.
  343. if ($type === self::RETURN_METHOD) {
  344. $this->_internalIpcData['_callType'] = self::RETURN_METHOD;
  345. } else {
  346. $this->_internalIpcData['_callType'] = self::VOID_METHOD;
  347. }
  348. // These setting are common to both the calling types
  349. $this->_internalIpcData['_callMethod'] = $methodName;
  350. $this->_internalIpcData['_callInput'] = $argList;
  351. // Write the IPC data to the shared segment
  352. $this->_writeToIpcSegment();
  353. // Now we need to differentiate a bit.
  354. switch ($this->_internalIpcData['_callType']) {
  355. case VOID_METHOD:
  356. // Notify the child so it can process the request
  357. $this->_sendSigUsr1();
  358. break;
  359. case RETURN_METHOD:
  360. // Set the semaphorew
  361. shmop_write($this->_internalSemKey, 1, 0);
  362. // Notify the child so it can process the request
  363. $this->_sendSigUsr1();
  364. // Block until the child process return
  365. $this->_waitForIpcSemaphore();
  366. // Read from the SHM segment. The result is stored into
  367. // $this->_internalIpcData['_callOutput']
  368. $this->_readFromIpcSegment();
  369. // Data are returned. Now we can reset the semaphore
  370. shmop_write($this->_internalSemKey, 0, 1);
  371. // Return the result. Hence no break required here
  372. return $this->_internalIpcData['_callOutput'];
  373. }
  374. }
  375. /**
  376. * This method actually implements the pseudo-thread logic.
  377. *
  378. * @return void
  379. */
  380. abstract protected function _run();
  381. /**
  382. * Sends signal to the child process
  383. *
  384. * @return void
  385. */
  386. private function _sendSigUsr1()
  387. {
  388. if ($this->_pid > 0) {
  389. posix_kill($this->_pid, SIGUSR1);
  390. }
  391. }
  392. /**
  393. * Acutally Write a variable to the shared memory segment
  394. *
  395. * @param string $name
  396. * @param mixed $value
  397. * @return void
  398. */
  399. private function _writeVariable($name, $value)
  400. {
  401. $this->_internalIpcData[$name] = $value;
  402. $this->_writeToIpcSegment();
  403. }
  404. /**
  405. * Destroy thread context and free relative resources.
  406. *
  407. * @return void
  408. */
  409. private function _cleanProcessContext()
  410. {
  411. shmop_delete($this->_internalIpcKey);
  412. shmop_delete($this->_internalSemKey);
  413. shmop_close($this->_internalIpcKey);
  414. shmop_close($this->_internalSemKey);
  415. @unlink($this->_ipcSegFile);
  416. @unlink($this->_ipcSemFile);
  417. $this->_isRunning = false;
  418. $this->_pid = null;
  419. }
  420. /**
  421. * This is the signal handler that makes the communications between client
  422. * and server possible.
  423. *
  424. * @param integer $signo
  425. * @return void
  426. */
  427. private function _sigHandler($signo)
  428. {
  429. switch ($signo) {
  430. case SIGTERM:
  431. // Handle shutdown tasks. Hence no break is require
  432. exit;
  433. case SIGUSR1:
  434. // This is the User-defined signal we'll use. Read the SHM segment
  435. $this->_readFromIpcSegment();
  436. if (isset($this->_internalIpcData['_callType'])) {
  437. $method = $this->_internalIpcData['_callMethod'];
  438. $params = $this->_internalIpcData['_callInput'];
  439. switch ($this->_internalIpcData['_callType']) {
  440. case self::VOID_METHOD:
  441. // Simple call the (void) method and return immediatly
  442. // no semaphore is placed into parent, so the processing
  443. // is async
  444. call_user_func(array($this, $method), $params);
  445. break;
  446. case self::RETURN_METHOD:
  447. // Process the request
  448. $this->_internalIpcData['_callOutput'] = call_user_func(array($this, $method), $params);
  449. // Write the result into IPC segment
  450. $this->_writeToIPCsegment();
  451. // Unlock the semaphore but block _writeToIpcSegment()
  452. shmop_write($this->_internalSemKey, 0, 0);
  453. shmop_write($this->_internalSemKey, 1, 1);
  454. break;
  455. }
  456. }
  457. break;
  458. default:
  459. // Ignore all other singals
  460. break;
  461. }
  462. }
  463. /**
  464. * Wait for IPC Semaphore
  465. *
  466. * @return void
  467. */
  468. private function _waitForIpcSemaphore()
  469. {
  470. while (true) {
  471. $okay = shmop_read($this->_internalSemKey, 0, 1);
  472. if ($okay === 0) {
  473. break;
  474. }
  475. usleep(10);
  476. }
  477. }
  478. /**
  479. * Read data from IPC segment
  480. *
  481. * @throws ZendX_Console_Process_Exception When writing of SHM segment fails
  482. * @return void
  483. */
  484. private function _readFromIpcSegment()
  485. {
  486. $serializedIpcData = shmop_read($this->_internalIpcKey,
  487. 0,
  488. shmop_size($this->_internalIpcKey));
  489. if ($serializedIpcData === false) {
  490. require_once 'ZendX/Console/Process/Exception.php';
  491. throw new ZendX_Console_Process_Exception('Fatal error while reading SHM segment');
  492. }
  493. $data = @unserialize($serializedIpcData);
  494. if ($data !== false) {
  495. $this->_internalIpcData = $data;
  496. }
  497. }
  498. /**
  499. * Write data to IPC segment
  500. *
  501. * @throws ZendX_Console_Process_Exception When writing of SHM segment fails
  502. * @return void
  503. */
  504. private function _writeToIpcSegment()
  505. {
  506. // Read the transaction bit (2 bit of _internalSemKey segment). If it's
  507. // value is 1, we're into the execution of a PHP_FORK_RETURN_METHOD, so
  508. // we must not write to segment (data corruption)
  509. if (shmop_read($this->_internalSemKey, 1, 1) === 1) {
  510. return;
  511. }
  512. $serializedIpcData = serialize($this->_internalIpcData);
  513. // Set the exchange array (IPC) into the shared segment
  514. $shmBytesWritten = shmop_write($this->_internalIpcKey,
  515. $serializedIpcData,
  516. 0);
  517. // Check if lenght of SHM segment is enougth to contain data
  518. if ($shmBytesWritten !== strlen($serializedIpcData)) {
  519. require_once 'ZendX/Console/Process/Exception.php';
  520. throw new ZendX_Console_Process_Exception('Fatal error while writing to SHM segment');
  521. }
  522. }
  523. /**
  524. * Create an IPC segment
  525. *
  526. * @throws ZendX_Console_Process_Exception When SHM segment can't be created
  527. * @return boolean
  528. */
  529. private function _createIpcSegment()
  530. {
  531. $this->_ipcSegFile = realpath(sys_get_temp_dir()) . '/' . rand() . $this->_name . '.shm';
  532. touch($this->_ipcSegFile);
  533. $shmKey = ftok($this->_ipcSegFile, 't');
  534. if ($shmKey === -1) {
  535. require_once 'ZendX/Console/Process/Exception.php';
  536. throw new ZendX_Console_Process_Exception('Could not create SHM segment');
  537. }
  538. $this->_internalIpcKey = @shmop_open($shmKey, 'c', 0644, 10240);
  539. if (!$this->_internalIpcKey) {
  540. @unlink($this->_ipcSegFile);
  541. return false;
  542. }
  543. return true;
  544. }
  545. /**
  546. * Create IPC semaphore
  547. *
  548. * @throws ZendX_Console_Process_Exception When semaphore can't be created
  549. * @return boolean
  550. */
  551. private function _createIpcSemaphore()
  552. {
  553. $this->_ipcSemFile = realpath(sys_get_temp_dir()) . '/' . rand() . $this->_name . '.sem';
  554. touch($this->_ipcSemFile);
  555. $semKey = ftok($this->_ipcSemFile, 't');
  556. if ($semKey === -1) {
  557. require_once 'ZendX/Console/Process/Exception.php';
  558. throw new ZendX_Console_Process_Exception('Could not create semaphore');
  559. }
  560. $this->_internalSemKey = @shmop_open($semKey, 'c', 0644, 10);
  561. if (!$this->_internalSemKey) {
  562. @unlink($this->_ipcSemFile);
  563. return false;
  564. }
  565. return true;
  566. }
  567. }