PageRenderTime 24ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/1.0/src/LibWebta/library/IO/PCNTL/class.ProcessManager.php

http://scalr.googlecode.com/
PHP | 322 lines | 173 code | 54 blank | 95 comment | 28 complexity | 9beb469262d7ed843f1a26d01458796b MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, GPL-3.0
  1. <?
  2. /**
  3. * This file is a part of LibWebta, PHP class library.
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to version 2 of the GPL license,
  8. * that is bundled with this package in the file license.txt and is
  9. * available through the world-wide-web at the following url:
  10. * http://www.gnu.org/copyleft/gpl.html
  11. *
  12. * @category LibWebta
  13. * @package IO
  14. * @subpackage PCNTL
  15. * @copyright Copyright (c) 2003-2007 Webta Inc, http://www.gnu.org/licenses/gpl.html
  16. * @license http://www.gnu.org/licenses/gpl.html
  17. */
  18. /**
  19. * @name ProcessManager
  20. * @category LibWebta
  21. * @package IO
  22. * @subpackage PCNTL
  23. * @version 1.0
  24. * @author Igor Savchenko <http://webta.net/company.html>
  25. * @example tests.php
  26. * @see tests.php
  27. */
  28. class ProcessManager extends Core
  29. {
  30. /**
  31. * SignalHandler
  32. *
  33. * @var SignalHandler
  34. * @access private
  35. */
  36. private $SignalHandler;
  37. /**
  38. * Proccess object
  39. *
  40. * @var object
  41. * @access protected
  42. */
  43. protected $ProcessObject;
  44. /**
  45. * PIDs of child processes
  46. *
  47. * @var array
  48. * @access public
  49. */
  50. public $PIDs;
  51. /**
  52. * Maximum allowed childs in one moment
  53. *
  54. * @var integer
  55. * @access public
  56. */
  57. public $MaxChilds;
  58. public $PIDDir;
  59. private $Logger;
  60. /**
  61. * Proccess manager Constructor
  62. *
  63. * @param SignalHandler $SignalHandler
  64. */
  65. function __construct(&$SignalHandler)
  66. {
  67. $this->Logger = LoggerManager::getLogger('ProcessManager');
  68. if ($SignalHandler instanceof SignalHandler)
  69. {
  70. $SignalHandler->ProcessManager = &$this;
  71. $this->SignalHandler = $SignalHandler;
  72. $this->MaxChilds = 5;
  73. $this->Logger->debug("Process initialized.");
  74. }
  75. else
  76. self::RaiseError("Invalid signal handler");
  77. }
  78. /**
  79. * Destructor
  80. * @ignore
  81. */
  82. function __destruct()
  83. {
  84. }
  85. /**
  86. * Set MaxChilds
  87. *
  88. * @param integer $num
  89. * @final
  90. */
  91. final public function SetMaxChilds($num)
  92. {
  93. if (count($this->PIDs) == 0)
  94. {
  95. $this->MaxChilds = $num;
  96. $this->Logger->debug("Number of MaxChilds set to {$num}");
  97. }
  98. else
  99. self::RaiseError("You can only set MaxChilds *before* you Run() is executed.");
  100. }
  101. public function SetPIDDir ($path)
  102. {
  103. $this->PIDDir = $path;
  104. }
  105. /**
  106. * Start Forking
  107. *
  108. * @param object $ProcessObject
  109. * @final
  110. */
  111. final public function Run(&$ProcessObject)
  112. {
  113. // Check for ProcessObject existence
  114. if (!is_object($ProcessObject) || !($ProcessObject instanceof IProcess))
  115. self::RaiseError("Invalid Proccess object", E_ERROR);
  116. // Set class property
  117. $this->ProcessObject = $ProcessObject;
  118. $pid = posix_getpid();
  119. if ($this->PIDDir)
  120. {
  121. $this->Logger->debug("Touch process PID file {$pid}");
  122. @touch("{$this->PIDDir}/{$pid}");
  123. }
  124. $this->Logger->debug("Executing 'OnStartForking' routine");
  125. // Run routines before threading
  126. $this->ProcessObject->OnStartForking();
  127. $this->Logger->debug("'OnStartForking' successfully executed.");
  128. if (count($this->ProcessObject->ThreadArgs) != 0)
  129. {
  130. // Add handlers to signals
  131. $this->SignalHandler->SetSignalHandlers();
  132. $this->Logger->debug("Executing ProcessObject::ForkThreads()");
  133. // Start Threading
  134. $this->ForkThreads();
  135. // Wait while threads working
  136. $iteration = 1;
  137. while (true)
  138. {
  139. if (count($this->PIDs) == 0)
  140. break;
  141. sleep(2);
  142. if ($iteration++ == 10)
  143. {
  144. $this->Logger->debug("Goin to MPWL. PIDs(".implode(", ", $this->PIDs).")");
  145. //
  146. // Zomby not needed.
  147. //
  148. $pid = pcntl_wait($status, WNOHANG | WUNTRACED);
  149. if ($pid > 0)
  150. {
  151. $this->Logger->debug("MPWL: pcntl_wait() from child with PID# {$pid} (Exit code: {$status})");
  152. foreach((array)$this->PIDs as $kk=>$vv)
  153. {
  154. if ($vv == $pid)
  155. {
  156. if ($this->PIDDir)
  157. {
  158. $this->Logger->debug("Delete thread PID file $pid");
  159. @unlink($this->PIDDir . "/" . $pid);
  160. }
  161. unset($this->PIDs[$kk]);
  162. }
  163. }
  164. $this->ForkThreads();
  165. }
  166. foreach ($this->PIDs as $k=>$pid)
  167. {
  168. $res = posix_kill($pid, 0);
  169. $this->Logger->debug("MPWL: Sending 0 signal to {$pid} = ".intval($res));
  170. if ($res === FALSE)
  171. {
  172. $this->Logger->debug("MPWL: Deleting '{$pid}' from PIDs query");
  173. if ($this->PIDDir)
  174. {
  175. $this->Logger->debug("Delete thread PID file {$this->PIDs[$k]}");
  176. @unlink($this->PIDDir . "/" . $this->PIDs[$k]);
  177. }
  178. unset($this->PIDs[$k]);
  179. }
  180. }
  181. $iteration = 1;
  182. }
  183. }
  184. }
  185. else
  186. {
  187. $this->Logger->debug("ProcessObject::ThreadArgs is empty. Nothing to do.");
  188. }
  189. $pid = posix_getpid();
  190. if ($this->PIDDir)
  191. {
  192. $this->Logger->debug("Delete Process PID file {$pid}");
  193. @unlink("{$this->PIDDir}/{$pid}");
  194. }
  195. $this->Logger->debug("All childs exited. Executing OnEndForking routine");
  196. // Run routines after forking
  197. $this->ProcessObject->OnEndForking();
  198. $this->Logger->debug("Main process complete. Exiting...");
  199. exit();
  200. }
  201. /**
  202. * Start forking processes while number of childs less than MaxChilds and we have data in ThreadArgs
  203. * @access private
  204. * @final
  205. */
  206. final public function ForkThreads()
  207. {
  208. while(count($this->ProcessObject->ThreadArgs) > 0 && count($this->PIDs) < $this->MaxChilds)
  209. {
  210. $arg = array_shift($this->ProcessObject->ThreadArgs);
  211. if ($arg)
  212. {
  213. $this->Fork($arg);
  214. usleep(500000);
  215. }
  216. }
  217. }
  218. /**
  219. * Fork child process
  220. *
  221. * @param mixed $arg
  222. * @final
  223. */
  224. final private function Fork($arg)
  225. {
  226. $pid = @pcntl_fork();
  227. if(!$pid)
  228. {
  229. try
  230. {
  231. if ($this->ProcessObject->IsDaemon)
  232. $this->DemonizeProcess();
  233. $this->ProcessObject->StartThread($arg);
  234. }
  235. catch (Exception $err)
  236. {
  237. $this->Logger->error($err->getMessage());
  238. }
  239. exit();
  240. }
  241. else
  242. {
  243. if ($this->PIDDir)
  244. {
  245. $this->Logger->debug("Touch thread PID file $pid");
  246. touch($this->PIDDir."/".$pid);
  247. }
  248. $this->Logger->debug("Child with PID# {$pid} successfully forked");
  249. $this->PIDs[] = $pid;
  250. }
  251. }
  252. final private function DemonizeProcess()
  253. {
  254. $this->Logger->debug("Daemonizing process...");
  255. // Make the current process a session leader.
  256. if (posix_setsid() == -1)
  257. {
  258. $this->Logger->fatal("posix_setsid() return -1");
  259. exit();
  260. }
  261. // We need wait for some time
  262. sleep(2);
  263. // Send signal about demonize this child
  264. posix_kill(posix_getppid(), SIGUSR2);
  265. $this->Logger->debug("Process successfully demonized.");
  266. }
  267. }
  268. ?>