PageRenderTime 25ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Net/Gearman/Worker.php

https://github.com/rrehbeindoi/net_gearman
PHP | 499 lines | 303 code | 29 blank | 167 comment | 13 complexity | 43f3c226c34abf4039ebbd78efb7fd99 MD5 | raw file
  1. <?php
  2. /**
  3. * Interface for Danga's Gearman job scheduling system
  4. *
  5. * PHP version 5.1.0+
  6. *
  7. * LICENSE: This source file is subject to the New BSD license that is
  8. * available through the world-wide-web at the following URI:
  9. * http://www.opensource.org/licenses/bsd-license.php. If you did not receive
  10. * a copy of the New BSD License and are unable to obtain it through the web,
  11. * please send a note to license@php.net so we can mail you a copy immediately.
  12. *
  13. * @category Net
  14. * @package Net_Gearman
  15. * @author Joe Stump <joe@joestump.net>
  16. * @copyright 2007-2008 Digg.com, Inc.
  17. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  18. * @version CVS: $Id$
  19. * @link http://pear.php.net/package/Net_Gearman
  20. * @link http://www.danga.com/gearman/
  21. */
  22. /**
  23. * Gearman worker class
  24. *
  25. * Run an instance of a worker to listen for jobs. It then manages the running
  26. * of jobs, etc.
  27. *
  28. * <code>
  29. * <?php
  30. *
  31. * $servers = array(
  32. * '127.0.0.1:7003',
  33. * '127.0.0.1:7004'
  34. * );
  35. *
  36. * $abilities = array('HelloWorld', 'Foo', 'Bar');
  37. *
  38. * try {
  39. * $worker = new Net_Gearman_Worker($servers);
  40. * foreach ($abilities as $ability) {
  41. * $worker->addAbility('HelloWorld');
  42. * }
  43. * $worker->beginWork();
  44. * } catch (Net_Gearman_Exception $e) {
  45. * echo $e->getMessage() . "\n";
  46. * exit;
  47. * }
  48. *
  49. * ?>
  50. * </code>
  51. *
  52. * @category Net
  53. * @package Net_Gearman
  54. * @author Joe Stump <joe@joestump.net>
  55. * @copyright 2007-2008 Digg.com, Inc.
  56. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  57. * @version Release: @package_version@
  58. * @link http://www.danga.com/gearman/
  59. * @see Net_Gearman_Job, Net_Gearman_Connection
  60. */
  61. class Net_Gearman_Worker
  62. {
  63. /**
  64. * Pool of connections to Gearman servers
  65. *
  66. * @var array $conn
  67. */
  68. protected $conn = array();
  69. /**
  70. * Pool of retry connections
  71. *
  72. * @var array $conn
  73. */
  74. protected $retryConn = array();
  75. /**
  76. * Pool of worker abilities
  77. *
  78. * @var array $conn
  79. */
  80. protected $abilities = array();
  81. /**
  82. * Parameters for job contructors, indexed by ability name
  83. *
  84. * @var array $initParams
  85. */
  86. protected $initParams = array();
  87. /**
  88. * Job Factory
  89. *
  90. * @var callback
  91. */
  92. protected $jobFactory = array('Net_Gearman_Job', 'factory');
  93. /**
  94. * Callbacks registered for this worker
  95. *
  96. * @var array $callback
  97. * @see Net_Gearman_Worker::JOB_START
  98. * @see Net_Gearman_Worker::JOB_COMPLETE
  99. * @see Net_Gearman_Worker::JOB_FAIL
  100. */
  101. protected $callback = array(
  102. self::JOB_START => array(),
  103. self::JOB_COMPLETE => array(),
  104. self::JOB_FAIL => array()
  105. );
  106. /**
  107. * Unique id for this worker
  108. *
  109. * @var string $id
  110. */
  111. protected $id = "";
  112. /**
  113. * Callback types
  114. *
  115. * @const integer JOB_START Ran when a job is started
  116. * @const integer JOB_COMPLETE Ran when a job is finished
  117. * @const integer JOB_FAIL Ran when a job fails
  118. */
  119. const JOB_START = 1;
  120. const JOB_COMPLETE = 2;
  121. const JOB_FAIL = 3;
  122. /**
  123. * Constructor
  124. *
  125. * @param array $servers List of servers to connect to
  126. * @param string $id Optional unique id for this worker
  127. *
  128. * @return void
  129. * @throws Net_Gearman_Exception
  130. * @see Net_Gearman_Connection
  131. */
  132. public function __construct($servers = null, $id = "")
  133. {
  134. if (is_null($servers)){
  135. $servers = array("localhost");
  136. } elseif (!is_array($servers) && strlen($servers)) {
  137. $servers = array($servers);
  138. } elseif (is_array($servers) && !count($servers)) {
  139. throw new Net_Gearman_Exception('Invalid servers specified');
  140. }
  141. if(empty($id)){
  142. $id = "pid_".getmypid()."_".uniqid();
  143. }
  144. $this->id = $id;
  145. foreach ($servers as $s) {
  146. try {
  147. $conn = Net_Gearman_Connection::connect($s);
  148. Net_Gearman_Connection::send($conn, "set_client_id", array("client_id" => $this->id));
  149. $this->conn[$s] = $conn;
  150. } catch (Net_Gearman_Exception $e) {
  151. $this->retryConn[$s] = time();
  152. }
  153. }
  154. if (empty($this->conn)) {
  155. throw new Net_Gearman_Exception(
  156. "Couldn't connect to any available servers"
  157. );
  158. }
  159. }
  160. /**
  161. * Set Job Factory
  162. *
  163. * Callback method
  164. * callback ( $name, $socket, $handle, $initParams = array() )
  165. *
  166. * param $name - function name
  167. * param $socket - server connection
  168. * param $handle - job handle
  169. * param $initParams - Parameters for job constructor as per addAbility
  170. *
  171. * @param callable
  172. * @return void
  173. * @throws
  174. */
  175. public function setJobFactory($callable)
  176. {
  177. if (!is_callable($callable)) {
  178. throw new InvalidArgumentException('Callback is not callable');
  179. }
  180. $this->jobFactory = $callable;
  181. }
  182. /**
  183. * Announce an ability to the job server
  184. *
  185. * @param string $ability Name of functcion/ability
  186. * @param integer $timeout How long to give this job
  187. * @param array $initParams Parameters for job constructor
  188. *
  189. * @return void
  190. * @see Net_Gearman_Connection::send()
  191. */
  192. public function addAbility($ability, $timeout = null, $initParams=array())
  193. {
  194. $call = 'can_do';
  195. $params = array('func' => $ability);
  196. if (is_int($timeout) && $timeout > 0) {
  197. $params['timeout'] = $timeout;
  198. $call = 'can_do_timeout';
  199. }
  200. $this->initParams[$ability] = $initParams;
  201. $this->abilities[$ability] = $timeout;
  202. foreach ($this->conn as $conn) {
  203. Net_Gearman_Connection::send($conn, $call, $params);
  204. }
  205. }
  206. /**
  207. * Begin working
  208. *
  209. * This starts the worker on its journey of actually working. The first
  210. * argument is a PHP callback to a function that can be used to monitor
  211. * the worker. If no callback is provided then the worker works until it
  212. * is killed. The monitor is passed two arguments; whether or not the
  213. * worker is idle and when the last job was ran.
  214. *
  215. * @param callback $monitor Function to monitor work
  216. *
  217. * @return void
  218. * @see Net_Gearman_Connection::send(), Net_Gearman_Connection::connect()
  219. * @see Net_Gearman_Worker::doWork(), Net_Gearman_Worker::addAbility()
  220. */
  221. public function beginWork($monitor = null)
  222. {
  223. if (!is_callable($monitor)) {
  224. $monitor = array($this, 'stopWork');
  225. }
  226. $write = null;
  227. $except = null;
  228. $working = true;
  229. $lastJob = time();
  230. $retryTime = 5;
  231. while ($working) {
  232. $sleep = true;
  233. $currentTime = time();
  234. foreach ($this->conn as $server => $socket) {
  235. $worked = false;
  236. try {
  237. $worked = $this->doWork($socket);
  238. } catch (Net_Gearman_Exception $e) {
  239. unset($this->conn[$server]);
  240. $this->retryConn[$server] = $currentTime;
  241. }
  242. if ($worked) {
  243. $lastJob = time();
  244. $sleep = false;
  245. }
  246. }
  247. $idle = false;
  248. if ($sleep && count($this->conn)) {
  249. foreach ($this->conn as $socket) {
  250. Net_Gearman_Connection::send($socket, 'pre_sleep');
  251. }
  252. $read = $this->conn;
  253. socket_select($read, $write, $except, 60);
  254. $idle = (count($read) == 0);
  255. }
  256. $retryChange = false;
  257. foreach ($this->retryConn as $s => $lastTry) {
  258. if (($lastTry + $retryTime) < $currentTime) {
  259. try {
  260. $conn = Net_Gearman_Connection::connect($s);
  261. $this->conn[$s] = $conn;
  262. $retryChange = true;
  263. unset($this->retryConn[$s]);
  264. Net_Gearman_Connection::send($conn, "set_client_id", array("client_id" => $this->id));
  265. } catch (Net_Gearman_Exception $e) {
  266. $this->retryConn[$s] = $currentTime;
  267. }
  268. }
  269. }
  270. if (count($this->conn) == 0) {
  271. // sleep to avoid wasted cpu cycles if no connections to block on using socket_select
  272. sleep(1);
  273. }
  274. if ($retryChange === true) {
  275. // broadcast all abilities to all servers
  276. foreach ($this->abilities as $ability => $timeout) {
  277. $this->addAbility(
  278. $ability, $timeout, $this->initParams[$ability]
  279. );
  280. }
  281. }
  282. if (call_user_func($monitor, $idle, $lastJob) == true) {
  283. $working = false;
  284. }
  285. }
  286. }
  287. /**
  288. * Listen on the socket for work
  289. *
  290. * Sends the 'grab_job' command and then listens for either the 'noop' or
  291. * the 'no_job' command to come back. If the 'job_assign' comes down the
  292. * pipe then we run that job.
  293. *
  294. * @param resource $socket The socket to work on
  295. *
  296. * @return boolean Returns true if work was done, false if not
  297. * @throws Net_Gearman_Exception
  298. * @see Net_Gearman_Connection::send()
  299. */
  300. protected function doWork($socket)
  301. {
  302. Net_Gearman_Connection::send($socket, 'grab_job');
  303. $resp = array('function' => 'noop');
  304. while (count($resp) && $resp['function'] == 'noop') {
  305. $resp = Net_Gearman_Connection::blockingRead($socket);
  306. }
  307. if (in_array($resp['function'], array('noop', 'no_job'))) {
  308. return false;
  309. }
  310. if ($resp['function'] != 'job_assign') {
  311. throw new Net_Gearman_Exception('Holy Cow! What are you doing?!');
  312. }
  313. $name = $resp['data']['func'];
  314. $handle = $resp['data']['handle'];
  315. $arg = array();
  316. if (isset($resp['data']['arg']) &&
  317. Net_Gearman_Connection::stringLength($resp['data']['arg'])) {
  318. $arg = json_decode($resp['data']['arg'], true);
  319. if($arg === null){
  320. $arg = $resp['data']['arg'];
  321. }
  322. }
  323. $job = call_user_func(
  324. $this->jobFactory, $name, $socket, $handle, $this->initParams[$name]
  325. );
  326. try {
  327. $this->start($handle, $name, $arg);
  328. $res = $job->run($arg);
  329. if (!is_array($res)) {
  330. $res = array('result' => $res);
  331. }
  332. $job->complete($res);
  333. $this->complete($handle, $name, $res);
  334. } catch (Net_Gearman_Job_Exception $e) {
  335. $job->fail();
  336. $this->fail($handle, $name, $e);
  337. }
  338. // Force the job's destructor to run
  339. $job = null;
  340. return true;
  341. }
  342. /**
  343. * Attach a callback
  344. *
  345. * @param callback $callback A valid PHP callback
  346. * @param integer $type Type of callback
  347. *
  348. * @return void
  349. * @throws Net_Gearman_Exception When an invalid callback is specified.
  350. * @throws Net_Gearman_Exception When an invalid type is specified.
  351. */
  352. public function attachCallback($callback, $type = self::JOB_COMPLETE)
  353. {
  354. if (!is_callable($callback)) {
  355. throw new Net_Gearman_Exception('Invalid callback specified');
  356. }
  357. if (!isset($this->callback[$type])) {
  358. throw new Net_Gearman_Exception('Invalid callback type specified.');
  359. }
  360. $this->callback[$type][] = $callback;
  361. }
  362. /**
  363. * Run the job start callbacks
  364. *
  365. * @param string $handle The job's Gearman handle
  366. * @param string $job The name of the job
  367. * @param mixed $args The job's argument list
  368. *
  369. * @return void
  370. */
  371. protected function start($handle, $job, $args)
  372. {
  373. if (count($this->callback[self::JOB_START]) == 0) {
  374. return; // No callbacks to run
  375. }
  376. foreach ($this->callback[self::JOB_START] as $callback) {
  377. call_user_func($callback, $handle, $job, $args);
  378. }
  379. }
  380. /**
  381. * Run the complete callbacks
  382. *
  383. * @param string $handle The job's Gearman handle
  384. * @param string $job The name of the job
  385. * @param array $result The job's returned result
  386. *
  387. * @return void
  388. */
  389. protected function complete($handle, $job, array $result)
  390. {
  391. if (count($this->callback[self::JOB_COMPLETE]) == 0) {
  392. return; // No callbacks to run
  393. }
  394. foreach ($this->callback[self::JOB_COMPLETE] as $callback) {
  395. call_user_func($callback, $handle, $job, $result);
  396. }
  397. }
  398. /**
  399. * Run the fail callbacks
  400. *
  401. * @param string $handle The job's Gearman handle
  402. * @param string $job The name of the job
  403. * @param object $error The exception thrown
  404. *
  405. * @return void
  406. */
  407. protected function fail($handle, $job, PEAR_Exception $error)
  408. {
  409. if (count($this->callback[self::JOB_FAIL]) == 0) {
  410. return; // No callbacks to run
  411. }
  412. foreach ($this->callback[self::JOB_FAIL] as $callback) {
  413. call_user_func($callback, $handle, $job, $error);
  414. }
  415. }
  416. /**
  417. * Stop working
  418. *
  419. * @return void
  420. */
  421. public function endWork()
  422. {
  423. foreach ($this->conn as $conn) {
  424. Net_Gearman_Connection::close($conn);
  425. }
  426. }
  427. /**
  428. * Destructor
  429. *
  430. * @return void
  431. * @see Net_Gearman_Worker::stop()
  432. */
  433. public function __destruct()
  434. {
  435. $this->endWork();
  436. }
  437. /**
  438. * Should we stop work?
  439. *
  440. * @return boolean
  441. */
  442. public function stopWork()
  443. {
  444. return false;
  445. }
  446. }