00001 <?php
00031 abstract class ZendX_Console_Process_Unix
00032 {
00036 const VOID_METHOD = 'void_method';
00037
00041 const RETURN_METHOD = 'void_method';
00042
00048 private $_name;
00049
00055 private $_pid = null;
00056
00062 private $_puid = null;
00063
00069 private $_guid = null;
00070
00076 private $_isRunning = false;
00077
00083 private $_isChild = false;
00084
00090 private $_internalIpcData = array();
00091
00097 private $_internalIpcKey;
00098
00104 private $_internalSemKey;
00105
00112 private $_ipcIsOkay;
00113
00119 private $_ipcSegFile;
00120
00126 private $_ipcSemFile;
00127
00144 public function __construct($puid = null, $guid = null, $umask = null)
00145 {
00146 if (substr(PHP_OS, 0, 3) === 'WIN') {
00147 require_once 'ZendX/Console/Process/Exception.php';
00148 throw new ZendX_Console_Process_Exception('Cannot run on windows');
00149 } else if (!in_array(substr(PHP_SAPI, 0, 3), array('cli', 'cgi'))) {
00150 require_once 'ZendX/Console/Process/Exception.php';
00151 throw new ZendX_Console_Process_Exception('Can only run on CLI or CGI enviroment');
00152 } else if (!function_exists('shmop_open')) {
00153 require_once 'ZendX/Console/Process/Exception.php';
00154 throw new ZendX_Console_Process_Exception('shmop_* functions are required');
00155 } else if (!function_exists('pcntl_fork')) {
00156 require_once 'ZendX/Console/Process/Exception.php';
00157 throw new ZendX_Console_Process_Exception('pcntl_* functions are required');
00158 } else if (!function_exists('posix_kill')) {
00159 require_once 'ZendX/Console/Process/Exception.php';
00160 throw new ZendX_Console_Process_Exception('posix_* functions are required');
00161 }
00162
00163 $this->_isRunning = false;
00164
00165 $this->_name = md5(uniqid(rand()));
00166 $this->_guid = $guid;
00167 $this->_puid = $puid;
00168
00169 if ($umask !== null) {
00170 umask($umask);
00171 }
00172
00173
00174
00175
00176 if ($this->_createIpcSegment() && $this->_createIpcSemaphore()) {
00177 $this->_ipcIsOkay = true;
00178 } else {
00179 $this->_ipcIsOkay = false;
00180 }
00181 }
00182
00186 public function __destruct()
00187 {
00188 if ($this->isRunning()) {
00189 $this->stop();
00190 }
00191 }
00192
00207 public function start()
00208 {
00209 if (!$this->_ipcIsOkay) {
00210 require_once 'ZendX/Console/Process/Exception.php';
00211 throw new ZendX_Console_Process_Exception('Unable to create SHM segments for process communications');
00212 }
00213
00214
00215 @ob_end_flush();
00216
00217 pcntl_signal(SIGCHLD, SIG_IGN);
00218
00219 $pid = @pcntl_fork();
00220 if ($pid === -1) {
00221 require_once 'ZendX/Console/Process/Exception.php';
00222 throw new ZendX_Console_Process_Exception('Forking process failed');
00223 } else if ($pid === 0) {
00224
00225 $this->_isChild = true;
00226
00227
00228 sleep(1);
00229
00230
00231 pcntl_signal(SIGUSR1, array($this, '_sigHandler'));
00232
00233
00234 if ($this->_guid !== null) {
00235 posix_setgid($this->_guid);
00236 }
00237
00238 if ($this->_puid !== null) {
00239 posix_setuid($this->_puid);
00240 }
00241
00242
00243 try {
00244 $this->_run();
00245 } catch (Exception $e) {
00246
00247
00248 }
00249
00250
00251
00252 exit(0);
00253 } else {
00254
00255 $this->_isChild = false;
00256 $this->_isRunning = true;
00257 $this->_pid = $pid;
00258 }
00259 }
00260
00269 public function stop()
00270 {
00271 $success = false;
00272
00273 if ($this->_pid > 0) {
00274 $status = 0;
00275
00276 posix_kill($this->_pid, 9);
00277 pcntl_waitpid($this->_pid, $status, WNOHANG);
00278 $success = pcntl_wifexited($status);
00279 $this->_cleanProcessContext();
00280 }
00281
00282 return $success;
00283 }
00284
00290 public function isRunning()
00291 {
00292 return $this->_isRunning;
00293 }
00294
00305 public function setVariable($name, $value)
00306 {
00307 if ($name[0] === '_') {
00308 require_once 'ZendX/Console/Process/Exception.php';
00309 throw new ZendX_Console_Process_Exception('Only internal functions may use underline (_) as variable prefix');
00310 }
00311
00312 $this->_writeVariable($name, $value);
00313 }
00314
00322 public function getVariable($name)
00323 {
00324 $this->_readFromIpcSegment();
00325
00326 if (isset($this->_internalIpcData[$name])) {
00327 return $this->_internalIpcData[$name];
00328 } else {
00329 return null;
00330 }
00331 }
00332
00343 public function getLastAlive()
00344 {
00345 $pingTime = $this->getVariable('_pingTime');
00346
00347 return ($pingTime === null ? 0 : (time() - $pingTime));
00348 }
00349
00355 public function getPid()
00356 {
00357 return $this->_pid;
00358 }
00359
00370 protected function _setAlive()
00371 {
00372 $this->_writeVariable('_pingTime', time());
00373 }
00374
00375
00385 protected function _callCallbackMethod($methodName, array $argList = array(), $type = self::VOID_METHOD)
00386 {
00387
00388
00389 if ($type === self::RETURN_METHOD) {
00390 $this->_internalIpcData['_callType'] = self::RETURN_METHOD;
00391 } else {
00392 $this->_internalIpcData['_callType'] = self::VOID_METHOD;
00393 }
00394
00395
00396 $this->_internalIpcData['_callMethod'] = $methodName;
00397 $this->_internalIpcData['_callInput'] = $argList;
00398
00399
00400 $this->_writeToIpcSegment();
00401
00402
00403 switch ($this->_internalIpcData['_callType']) {
00404 case VOID_METHOD:
00405
00406 $this->_sendSigUsr1();
00407 break;
00408
00409 case RETURN_METHOD:
00410
00411 shmop_write($this->_internalSemKey, 1, 0);
00412
00413
00414 $this->_sendSigUsr1();
00415
00416
00417 $this->_waitForIpcSemaphore();
00418
00419
00420
00421 $this->_readFromIpcSegment();
00422
00423
00424 shmop_write($this->_internalSemKey, 0, 1);
00425
00426
00427 return $this->_internalIpcData['_callOutput'];
00428 }
00429 }
00430
00436 abstract protected function _run();
00437
00443 private function _sendSigUsr1()
00444 {
00445 if ($this->_pid > 0) {
00446 posix_kill($this->_pid, SIGUSR1);
00447 }
00448 }
00449
00457 private function _writeVariable($name, $value)
00458 {
00459 $this->_internalIpcData[$name] = $value;
00460 $this->_writeToIpcSegment();
00461 }
00462
00468 private function _cleanProcessContext()
00469 {
00470 shmop_delete($this->_internalIpcKey);
00471 shmop_delete($this->_internalSemKey);
00472
00473 shmop_close($this->_internalIpcKey);
00474 shmop_close($this->_internalSemKey);
00475
00476 @unlink($this->_ipcSegFile);
00477 @unlink($this->_ipcSemFile);
00478
00479 $this->_isRunning = false;
00480 $this->_pid = null;
00481 }
00482
00490 private function _sigHandler($signo)
00491 {
00492 switch ($signo) {
00493 case SIGTERM:
00494
00495 exit;
00496
00497 case SIGUSR1:
00498
00499 $this->_readFromIpcSegment();
00500
00501 if (isset($this->_internalIpcData['_callType'])) {
00502 $method = $this->_internalIpcData['_callMethod'];
00503 $params = $this->_internalIpcData['_callInput'];
00504
00505 switch ($this->_internalIpcData['_callType']) {
00506 case self::VOID_METHOD:
00507
00508
00509
00510 call_user_func(array($this, $method), $params);
00511 break;
00512
00513 case self::RETURN_METHOD:
00514
00515 $this->_internalIpcData['_callOutput'] = call_user_func(array($this, $method), $params);
00516
00517
00518 $this->_writeToIPCsegment();
00519
00520
00521 shmop_write($this->_internalSemKey, 0, 0);
00522 shmop_write($this->_internalSemKey, 1, 1);
00523 break;
00524 }
00525 }
00526 break;
00527
00528 default:
00529
00530 break;
00531 }
00532 }
00533
00539 private function _waitForIpcSemaphore()
00540 {
00541 while (true) {
00542 $okay = shmop_read($this->_internalSemKey, 0, 1);
00543
00544 if ($okay === 0) {
00545 break;
00546 }
00547
00548 usleep(10);
00549 }
00550 }
00551
00558 private function _readFromIpcSegment()
00559 {
00560 $serializedIpcData = shmop_read($this->_internalIpcKey,
00561 0,
00562 shmop_size($this->_internalIpcKey));
00563
00564 if ($serializedIpcData === false) {
00565 require_once 'ZendX/Console/Process/Exception.php';
00566 throw new ZendX_Console_Process_Exception('Fatal error while reading SHM segment');
00567 }
00568
00569 $data = @unserialize($serializedIpcData);
00570
00571 if ($data !== false) {
00572 $this->_internalIpcData = $data;
00573 }
00574 }
00575
00582 private function _writeToIpcSegment()
00583 {
00584
00585
00586
00587 if (shmop_read($this->_internalSemKey, 1, 1) === 1) {
00588 return;
00589 }
00590
00591 $serializedIpcData = serialize($this->_internalIpcData);
00592
00593
00594 $shmBytesWritten = shmop_write($this->_internalIpcKey,
00595 $serializedIpcData,
00596 0);
00597
00598
00599 if ($shmBytesWritten !== strlen($serializedIpcData)) {
00600 require_once 'ZendX/Console/Process/Exception.php';
00601 throw new ZendX_Console_Process_Exception('Fatal error while writing to SHM segment');
00602 }
00603 }
00604
00611 private function _createIpcSegment()
00612 {
00613 $this->_ipcSegFile = realpath(sys_get_temp_dir()) . '/' . rand() . $this->_name . '.shm';
00614 touch($this->_ipcSegFile);
00615
00616 $shmKey = ftok($this->_ipcSegFile, 't');
00617 if ($shmKey === -1) {
00618 require_once 'ZendX/Console/Process/Exception.php';
00619 throw new ZendX_Console_Process_Exception('Could not create SHM segment');
00620 }
00621
00622 $this->_internalIpcKey = @shmop_open($shmKey, 'c', 0644, 10240);
00623
00624 if (!$this->_internalIpcKey) {
00625 @unlink($this->_ipcSegFile);
00626 return false;
00627 }
00628
00629 return true;
00630 }
00631
00638 private function _createIpcSemaphore()
00639 {
00640 $this->_ipcSemFile = realpath(sys_get_temp_dir()) . '/' . rand() . $this->_name . '.sem';
00641 touch($this->_ipcSemFile);
00642
00643 $semKey = ftok($this->_ipcSemFile, 't');
00644 if ($semKey === -1) {
00645 require_once 'ZendX/Console/Process/Exception.php';
00646 throw new ZendX_Console_Process_Exception('Could not create semaphore');
00647 }
00648
00649 $this->_internalSemKey = @shmop_open($semKey, 'c', 0644, 10);
00650
00651 if (!$this->_internalSemKey) {
00652 @unlink($this->_ipcSemFile);
00653 return false;
00654 }
00655
00656 return true;
00657 }
00658 }