MediaWiki master
JobQueueGroup.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\JobQueue;
8
9use InvalidArgumentException;
10use LogicException;
20
33 protected $cache;
34
36 protected $domain;
38 protected $readOnlyMode;
40 private $localJobClasses;
42 private $jobTypeConfiguration;
44 private $jobTypesExcludedFromDefaultQueue;
46 private $statsFactory;
48 private $localClusterCache;
50 private $globalIdGenerator;
51
54
55 public const TYPE_DEFAULT = 1; // integer; jobs popped by default
56 private const TYPE_ANY = 2; // integer; any job
57
58 public const USE_CACHE = 1; // integer; use process or persistent cache
59
60 private const PROC_CACHE_TTL = 15; // integer; seconds
61
72 public function __construct(
73 $domain,
75 ?array $localJobClasses,
76 array $jobTypeConfiguration,
77 array $jobTypesExcludedFromDefaultQueue,
78 StatsFactory $statsFactory,
79 BagOStuff $localClusterCache,
80 GlobalIdGenerator $globalIdGenerator
81 ) {
82 $this->domain = $domain;
83 $this->readOnlyMode = $readOnlyMode;
84 $this->cache = new MapCacheLRU( 10 );
85 $this->localJobClasses = $localJobClasses;
86 $this->jobTypeConfiguration = $jobTypeConfiguration;
87 $this->jobTypesExcludedFromDefaultQueue = $jobTypesExcludedFromDefaultQueue;
88 $this->statsFactory = $statsFactory;
89 $this->localClusterCache = $localClusterCache;
90 $this->globalIdGenerator = $globalIdGenerator;
91 }
92
99 public function get( $type ) {
100 $conf = [ 'domain' => $this->domain, 'type' => $type ];
101 $conf += $this->jobTypeConfiguration[$type] ?? $this->jobTypeConfiguration['default'];
102 if ( !isset( $conf['readOnlyReason'] ) ) {
103 $conf['readOnlyReason'] = $this->readOnlyMode->getConfiguredReason();
104 }
105
106 $conf['stats'] = $this->statsFactory;
107 $conf['localClusterCache'] = $this->localClusterCache;
108 $conf['idGenerator'] = $this->globalIdGenerator;
109
110 return JobQueue::factory( $conf );
111 }
112
122 public function push( $jobs ) {
123 $jobs = is_array( $jobs ) ? $jobs : [ $jobs ];
124 if ( $jobs === [] ) {
125 return;
126 }
127
128 $this->assertValidJobs( $jobs );
129
130 $jobsByType = []; // (job type => list of jobs)
131 foreach ( $jobs as $job ) {
132 $type = $job->getType();
133 if ( isset( $this->jobTypeConfiguration[$type] ) ) {
134 $jobsByType[$type][] = $job;
135 } else {
136 if (
137 isset( $this->jobTypeConfiguration['default']['typeAgnostic'] ) &&
138 $this->jobTypeConfiguration['default']['typeAgnostic']
139 ) {
140 $jobsByType['default'][] = $job;
141 } else {
142 $jobsByType[$type][] = $job;
143 }
144 }
145 }
146
147 foreach ( $jobsByType as $type => $jobs ) {
148 $this->get( $type )->push( $jobs );
149 }
150
151 if ( $this->cache->hasField( 'queues-ready', 'list' ) ) {
152 $list = $this->cache->getField( 'queues-ready', 'list' );
153 if ( count( array_diff( array_keys( $jobsByType ), $list ) ) ) {
154 $this->cache->clear( 'queues-ready' );
155 }
156 }
157
158 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getLocalClusterInstance();
159 $cache->set(
160 $cache->makeGlobalKey( 'jobqueue-hasjobs', $this->domain, self::TYPE_ANY ),
161 'true',
162 15
163 );
164 if ( array_diff( array_keys( $jobsByType ), $this->jobTypesExcludedFromDefaultQueue ) ) {
165 $cache->set(
166 $cache->makeGlobalKey( 'jobqueue-hasjobs', $this->domain, self::TYPE_DEFAULT ),
167 'true',
168 15
169 );
170 }
171 }
172
180 public function lazyPush( $jobs ) {
181 if ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ) {
182 $this->push( $jobs );
183 return;
184 }
185
186 $jobs = is_array( $jobs ) ? $jobs : [ $jobs ];
187
188 // Throw errors now instead of on push(), when other jobs may be buffered
189 $this->assertValidJobs( $jobs );
190
191 DeferredUpdates::addUpdate( new JobQueueEnqueueUpdate( $this->domain, $jobs ) );
192 }
193
208 public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0, array $ignored = [] ) {
209 $job = false;
210
211 if ( !$this->localJobClasses ) {
212 throw new JobQueueError(
213 "Cannot pop '{$qtype}' job off foreign '{$this->domain}' wiki queue." );
214 }
215 if ( is_string( $qtype ) && !isset( $this->localJobClasses[$qtype] ) ) {
216 // Do not pop jobs if there is no class for the queue type
217 throw new JobQueueError( "Unrecognized job type '$qtype'." );
218 }
219
220 if ( is_string( $qtype ) ) { // specific job type
221 if ( !in_array( $qtype, $ignored ) ) {
222 $job = $this->get( $qtype )->pop();
223 }
224 } else { // any job in the "default" jobs types
225 if ( $flags & self::USE_CACHE ) {
226 if ( !$this->cache->hasField( 'queues-ready', 'list', self::PROC_CACHE_TTL ) ) {
227 $this->cache->setField( 'queues-ready', 'list', $this->getQueuesWithJobs() );
228 }
229 $types = $this->cache->getField( 'queues-ready', 'list' );
230 } else {
231 $types = $this->getQueuesWithJobs();
232 }
233
234 if ( $qtype == self::TYPE_DEFAULT ) {
235 $types = array_intersect( $types, $this->getDefaultQueueTypes() );
236 }
237
238 $types = array_diff( $types, $ignored ); // avoid selected types
239 shuffle( $types ); // avoid starvation
240
241 foreach ( $types as $type ) { // for each queue...
242 $job = $this->get( $type )->pop();
243 if ( $job ) { // found
244 break;
245 } else { // not found
246 $this->cache->clear( 'queues-ready' );
247 }
248 }
249 }
250
251 return $job;
252 }
253
260 public function ack( RunnableJob $job ) {
261 $this->get( $job->getType() )->ack( $job );
262 }
263
271 public function getQueueTypes() {
272 if ( !$this->localJobClasses ) {
273 throw new LogicException( 'Cannot inspect job queue from foreign wiki' );
274 }
275 return array_keys( $this->localJobClasses );
276 }
277
285 public function getDefaultQueueTypes() {
286 return array_diff( $this->getQueueTypes(), $this->jobTypesExcludedFromDefaultQueue );
287 }
288
298 public function queuesHaveJobs( $type = self::TYPE_ANY ) {
299 $cache = MediaWikiServices::getInstance()->getObjectCacheFactory()->getLocalClusterInstance();
300 $key = $cache->makeGlobalKey( 'jobqueue-hasjobs', $this->domain, $type );
301
302 $value = $cache->get( $key );
303 if ( $value === false ) {
304 $queues = $this->getQueuesWithJobs();
305 if ( $type == self::TYPE_DEFAULT ) {
306 $queues = array_intersect( $queues, $this->getDefaultQueueTypes() );
307 }
308 $value = count( $queues ) ? 'true' : 'false';
309 $cache->add( $key, $value, 15 );
310 }
311
312 return ( $value === 'true' );
313 }
314
322 public function getQueuesWithJobs() {
323 $types = [];
324 foreach ( $this->getCoalescedQueues() as $info ) {
326 $queue = $info['queue'];
327 $nonEmpty = $queue->getSiblingQueuesWithJobs( $this->getQueueTypes() );
328 if ( is_array( $nonEmpty ) ) { // batching features supported
329 $types = array_merge( $types, $nonEmpty );
330 } else { // we have to go through the queues in the bucket one-by-one
331 foreach ( $info['types'] as $type ) {
332 if ( !$this->get( $type )->isEmpty() ) {
333 $types[] = $type;
334 }
335 }
336 }
337 }
338
339 return $types;
340 }
341
349 public function getQueueSizes() {
350 $sizeMap = [];
351 foreach ( $this->getCoalescedQueues() as $info ) {
353 $queue = $info['queue'];
354 $sizes = $queue->getSiblingQueueSizes( $this->getQueueTypes() );
355 if ( is_array( $sizes ) ) { // batching features supported
356 $sizeMap += $sizes;
357 } else { // we have to go through the queues in the bucket one-by-one
358 foreach ( $info['types'] as $type ) {
359 $sizeMap[$type] = $this->get( $type )->getSize();
360 }
361 }
362 }
363
364 return $sizeMap;
365 }
366
371 protected function getCoalescedQueues() {
372 if ( $this->coalescedQueues === null ) {
373 $this->coalescedQueues = [];
374 foreach ( $this->jobTypeConfiguration as $type => $conf ) {
375 $conf['domain'] = $this->domain;
376 $conf['type'] = 'null';
377 $conf['stats'] = $this->statsFactory;
378 $conf['localClusterCache'] = $this->localClusterCache;
379 $conf['idGenerator'] = $this->globalIdGenerator;
380
381 $queue = JobQueue::factory( $conf );
382 $loc = $queue->getCoalesceLocationInternal() ?? '';
383 if ( !isset( $this->coalescedQueues[$loc] ) ) {
384 $this->coalescedQueues[$loc]['queue'] = $queue;
385 $this->coalescedQueues[$loc]['types'] = [];
386 }
387 if ( $type === 'default' ) {
388 $this->coalescedQueues[$loc]['types'] = array_merge(
389 $this->coalescedQueues[$loc]['types'],
390 array_diff( $this->getQueueTypes(), array_keys( $this->jobTypeConfiguration ) )
391 );
392 } else {
393 $this->coalescedQueues[$loc]['types'][] = $type;
394 }
395 }
396 }
397
399 }
400
401 private function assertValidJobs( array $jobs ) {
402 foreach ( $jobs as $job ) {
403 if ( !( $job instanceof IJobSpecification ) ) {
404 $type = get_debug_type( $job );
405 throw new InvalidArgumentException( "Expected IJobSpecification objects, got " . $type );
406 }
407 }
408 }
409}
410
412class_alias( JobQueueGroup::class, 'JobQueueGroup' );
Defer callable updates to run later in the PHP process.
Enqueue lazy-pushed jobs that have accumulated from JobQueueGroup.
Handle enqueueing of background jobs.
ReadOnlyMode $readOnlyMode
Read only mode.
getQueueSizes()
Get the size of the queues for a list of job types.
array $coalescedQueues
Map of (bucket => (queue => JobQueue, types => list of types)
push( $jobs)
Insert jobs into the respective queues of which they belong.
string $domain
Wiki domain ID.
ack(RunnableJob $job)
Acknowledge that a job was completed.
pop( $qtype=self::TYPE_DEFAULT, $flags=0, array $ignored=[])
Pop one job off a job queue.
__construct( $domain, ReadOnlyMode $readOnlyMode, ?array $localJobClasses, array $jobTypeConfiguration, array $jobTypesExcludedFromDefaultQueue, StatsFactory $statsFactory, BagOStuff $localClusterCache, GlobalIdGenerator $globalIdGenerator)
queuesHaveJobs( $type=self::TYPE_ANY)
Check if there are any queues with jobs (this is cached)
getQueuesWithJobs()
Get the list of job types that have non-empty queues.
lazyPush( $jobs)
Buffer jobs for insertion via push() or call it now if in CLI mode.
getQueueTypes()
Get the list of queue types.
getDefaultQueueTypes()
Get the list of default queue types.
static factory(array $params)
Get a job queue object of the specified type.
Definition JobQueue.php:144
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
Store key-value entries in a size-limited in-memory LRU cache.
set( $key, $value, $rank=self::RANK_TOP)
Set a key/value pair.
get( $key, $maxAge=INF, $default=null)
Get the value for a key.
Determine whether a site is currently in read-only mode.
This is the primary interface for validating metrics definitions, caching defined metrics,...
Class for getting statistically unique IDs without a central coordinator.
Interface for serializable objects that describe a job queue task.
Job that has a run() method and metadata accessors for JobQueue::pop() and JobQueue::ack().
if(count( $args)< 1) $job