PageRenderTime 24ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/gaia/job/runner.php

https://github.com/gaiaops/gaia_core_php
PHP | 421 lines | 258 code | 53 blank | 110 comment | 51 complexity | f7daeb0840b0d213188b6674e3e09418 MD5 | raw file
  1. <?php
  2. namespace Gaia\Job;
  3. use Gaia\Job;
  4. use Gaia\Debugger;
  5. use Gaia\Http;
  6. use Gaia\Time;
  7. // +---------------------------------------------------------------------------+
  8. // | This file is part of the Job Framework. |
  9. // | Author 72squared (john@gaiaonline.com) |
  10. // +---------------------------------------------------------------------------+
  11. /**
  12. * Job Runner Class.
  13. * Allows us to dequeue and run jobs from the queue in a non-blocking fashion.
  14. */
  15. class Runner {
  16. /**
  17. * @type int How many jobs processed simultaneously?
  18. */
  19. protected $max = 3;
  20. /**
  21. * How many jobs should we process before we shutdown?
  22. */
  23. protected $limit = 0;
  24. /**
  25. * how many seconds to run before shutting down.
  26. */
  27. protected $timelimit = 0;
  28. /**
  29. * keep track of the time we started.
  30. */
  31. protected $start = 0;
  32. /**
  33. * @type array list of job objects waiting
  34. */
  35. protected $queue = array();
  36. /**
  37. * @type bool flag for if we want to keep running.
  38. */
  39. protected $active = TRUE;
  40. /**
  41. * @type int how many jobs processed so far?
  42. */
  43. protected $processed = 0;
  44. /**
  45. * how many jobs dequeued?
  46. */
  47. protected $dequeued = 0;
  48. /**
  49. * @type int how many failures?
  50. */
  51. protected $failed = 0;
  52. /**
  53. * @type int how many no repies?
  54. */
  55. protected $noreplies = 0;
  56. /**
  57. * When did we last run the tasks?
  58. */
  59. protected $lastrun = 0;
  60. /**
  61. * @type int debugger
  62. */
  63. protected $debug = NULL;
  64. /**
  65. * the http pool object.
  66. */
  67. protected $pool;
  68. /**
  69. * a list of callbacks to run every few seconds.
  70. */
  71. protected $tasks = array();
  72. /**
  73. * when dequeueing use this pattern to match which queues to watch.
  74. */
  75. protected $watch = '*';
  76. /**
  77. * class constructor. Optionally pass in an http pool object.
  78. */
  79. public function __construct( Http\Pool $pool = NULL ){
  80. $this->pool = ( $pool ) ? $pool : new Http\Pool;
  81. $runner = $this;
  82. $closure = function( Http\Request $job ) use ($runner ) {
  83. return $runner->handle( $job );
  84. };
  85. $this->pool->attach( $closure );
  86. }
  87. /**
  88. * add a task.
  89. */
  90. public function addTask( \Closure $closure ){
  91. $this->tasks[] = $closure;
  92. }
  93. /**
  94. * specify a pattern for which queues to watch.
  95. */
  96. public function watch( $pattern ){
  97. $this->watch = $pattern;
  98. }
  99. /**
  100. * start process the jobs in the queue.
  101. */
  102. public function process(){
  103. $this->active = TRUE;
  104. if( ! $this->start ) $this->start = Time::now();
  105. $this->populate();
  106. while( $this->active ){
  107. if( $this->limit && $this->dequeued >= $this->limit ){
  108. return $this->shutdown();
  109. }
  110. $time = Time::now();
  111. if( $this->timelimit && ($this->start + $this->timelimit) < $time ){
  112. return $this->shutdown();
  113. }
  114. if( ( $time - 2 ) >= $this->lastrun ) {
  115. $this->debug('maintenance tasks');
  116. if( $this->debug) $this->debug('jobs running: ' . count( $this->pool->requests() ) );
  117. $this->lastrun = $time;
  118. try {
  119. $this->populate();
  120. foreach( $this->tasks as $closure ) $closure( $this );
  121. } catch( Exception $e ){
  122. $this->debug( $e->__toString());
  123. }
  124. }
  125. if( $this->active && ! $this->pool->select(0.2) ) {
  126. usleep(200000);
  127. $this->populate();
  128. }
  129. }
  130. }
  131. /**
  132. * is the job runner still active?
  133. */
  134. public function isActive(){
  135. return $this->active;
  136. }
  137. /**
  138. * get a list of stats.
  139. */
  140. public function stats(){
  141. return array(
  142. 'uptime'=> Time::now() - $this->start,
  143. 'status'=> ($this->active ? 'running' : 'shutdown'),
  144. 'running'=> count( $this->pool->requests() ),
  145. 'queued' => count( $this->queue ),
  146. 'processed'=>$this->processed,
  147. 'failed'=>$this->failed,
  148. 'noreplies'=>$this->noreplies,
  149. );
  150. }
  151. /**
  152. * add a job to the pool
  153. */
  154. public function addJob( Job $job, $opts = array() ){
  155. return $this->pool->add( $job, $opts );
  156. }
  157. /**
  158. * make sure we are watching the right queues.
  159. */
  160. protected function syncWatch($conn){
  161. $watch = $ignore = array();
  162. $config = Job::config();
  163. $block_patterns = array();
  164. foreach( $config->queueRates() as $pattern => $rate){
  165. if( $rate >= mt_rand(1, 100) ) continue;
  166. $block_patterns[] = $pattern;
  167. }
  168. $debug = $this->debug;
  169. $pattern = $this->buildTubePattern( $this->watch );
  170. //print "\n$pattern\n";
  171. $allow = function( $tube ) use ($pattern, $block_patterns, $debug ){
  172. if( ! preg_match( $pattern, $tube, $match ) ) return FALSE;
  173. $queue = $match[1];
  174. $date = array_pop($match);
  175. $now = Time::now();
  176. $max_date = date('Ymd', $now);
  177. $min_date = date('Ymd', $now - (86400 * 3));
  178. if( $date > $max_date ) return FALSE;
  179. if( $date < $min_date ) return FALSE;
  180. if( ! $block_patterns ) return TRUE;
  181. foreach( $block_patterns as $pattern ){
  182. if( fnmatch( $pattern, $queue ) ) {
  183. if( $debug ) call_user_func( $debug, "blocking $tube");
  184. return FALSE;
  185. }
  186. }
  187. return TRUE;
  188. };
  189. foreach( $conn->listTubes() as $tube ) {
  190. if( $allow( $tube ) ) {
  191. $watch[] = $tube;
  192. } else {
  193. $ignore[] = $tube;
  194. }
  195. }
  196. if( count( $watch ) < 1 ) return FALSE;
  197. foreach( $watch as $tube ) $conn->watch( $tube );
  198. foreach( $ignore as $tube ) $conn->ignore( $tube );
  199. return TRUE;
  200. }
  201. /**
  202. * remove all of the jobs from a given queue.
  203. *
  204. */
  205. public function flush( $pattern = '*', $start = NULL, $end = NULL ){
  206. $tubes = $this->shardsByRange( $pattern, $start, $end );
  207. $ct = 0;
  208. $config = Job::config();
  209. $conns = $config->connections();
  210. foreach( $conns as $conn ){
  211. foreach( $tubes as $tube ){
  212. foreach( array( 'buried', 'delayed', 'ready' ) as $type ){
  213. $cmd = 'peek' . $type;
  214. try {
  215. while( $res = $conn->$cmd( $tube ) ) {
  216. $conn->delete( $res );
  217. $ct++;
  218. }
  219. } catch( \Exception $e ){}
  220. }
  221. }
  222. }
  223. return $ct;
  224. }
  225. public function shardsByRange( $pattern, $start = NULL, $end = NULL ){
  226. if( $end && $end < $start ) $end = $start;
  227. $start = ( $start ) ? date('Ymd', strtotime($start) ) : '';
  228. $end = ( $end ) ? date('Ymd', strtotime($end) ) : '';
  229. $tubes = array();
  230. $config = Job::config();
  231. $conns = $config->connections();
  232. $pattern = $this->buildTubePattern( $pattern );
  233. foreach( $conns as $conn ){
  234. foreach( $conn->listTubes() as $tube ) {
  235. if( preg_match($pattern, $tube, $match ) ) {
  236. $date = array_pop($match);
  237. if( $start && $start > $date ) continue;
  238. if( $end && $date >= $end ) continue;
  239. $tubes[ $tube ] = true;
  240. }
  241. }
  242. }
  243. return array_keys( $tubes );
  244. }
  245. public function flushOld( $pattern = '*' ){
  246. return $this->flush( $pattern, NULL, Time::now() - Job::config()->ttl() );
  247. }
  248. /**
  249. * Set the max number of jobs to process simultaneously.
  250. * @param int
  251. * @return int
  252. */
  253. public function setMax( $v ){
  254. return $this->max = intval( $v );
  255. }
  256. /**
  257. * Set the limit number of jobs to process.
  258. * @param int
  259. * @return int
  260. */
  261. public function setLimit( $v ){
  262. return $this->limit = intval( $v );
  263. }
  264. /**
  265. * how many secs do we want to run before quitting.
  266. * 0 means run forever.
  267. */
  268. public function setTimeLimit( $v ){
  269. return $this->timelimit = intval( $v );
  270. }
  271. /**
  272. * populate jobs into the system.
  273. * @return boolean
  274. */
  275. public function populate(){
  276. try {
  277. if( ! $this->active && ! $this->pool->requests() ){
  278. return TRUE;
  279. }
  280. if( $this->limit && $this->dequeued >= $this->limit ){
  281. return $this->shutdown();
  282. }
  283. if( $this->active && count( $this->queue ) < $this->max ){
  284. $ct = $this->max;
  285. if( $this->debug) $this->debug('starting dequeue: '.count( $this->queue ));
  286. $config = Job::config();
  287. $conns = $config->connections();
  288. $keys = array_keys( $conns );
  289. shuffle( $keys );
  290. foreach( $keys as $key ){
  291. $conn = $conns[ $key ];
  292. if( ! $this->syncWatch( $conn ) ) continue;
  293. while( $res = $conn->reserve(0) ){
  294. $id = $conn->hostInfo() . '-' . $res->getId();
  295. $job = new Job( @json_decode($res->getData(), TRUE) );
  296. if( ! $job->url ) {
  297. $conn->delete( $res );
  298. continue;
  299. }
  300. $job->id = $id;
  301. $this->queue[ $id ] = $job;
  302. $this->dequeued++;
  303. if( $this->limit && $this->dequeued >= $this->limit ) break 2;
  304. if( $ct-- < 1 ) break 2;
  305. }
  306. }
  307. if( $this->debug) $this->debug('ending dequeue: ' . count( $this->queue ) );
  308. }
  309. while( count( $this->pool->requests() ) < $this->max ){
  310. if( ! ( $job = array_shift( $this->queue ) ) ) break;
  311. if( ! $job instanceof Job ) {
  312. $this->debug( $job );
  313. continue;
  314. }
  315. try {
  316. $this->pool->add( $job );
  317. } catch( Exception $e ){
  318. $this->debug( $e );
  319. }
  320. }
  321. return TRUE;
  322. } catch( Exception $e ){
  323. $this->debug( $e );
  324. return FALSE;
  325. }
  326. }
  327. /**
  328. * don't allow any more jobs to be dequeued. start orderly shutdown.
  329. */
  330. public function shutdown()
  331. {
  332. if( ! $this->active ) return;
  333. $this->active = FALSE;
  334. while( $job = array_pop( $this->queue ) ) $this->pool->add( $job );
  335. $this->debug('calling http\pool::finish');
  336. $this->pool->finish();
  337. $this->debug('http\pool::finish done');
  338. }
  339. /**
  340. * handle a job that was run
  341. */
  342. public function handle( Http\Request $job ){
  343. $this->populate();
  344. if( ! $job instanceof Job ) return;
  345. if( $job->id ) $this->processed++;
  346. if( $job->response->http_code != 200 ) {
  347. if( $job->id ){
  348. ($job->response->http_code == 0 && $job->response->size_download == 0 ) ?
  349. $this->noreplies++ : $this->failed++;
  350. }
  351. }
  352. }
  353. /**
  354. * attach a debugger.
  355. */
  356. public function attachDebugger( \Closure $debug ){
  357. $this->debug = $debug;
  358. }
  359. protected function debug( $message ){
  360. if( ! $call = $this->debug ) return;
  361. return $call( $message );
  362. }
  363. protected function buildTubePattern( $v ){
  364. $prefix = Job::config()->queuePrefix();
  365. return '#^' . preg_quote($prefix, '#') . '(' . str_replace('\*', '([^\n]+)?', preg_quote($v, '#')) . ')' . '_([\d]{8})$#';
  366. }
  367. }
  368. // EOC