28 use Wikimedia\ScopedCallback;
60 parent::__construct( $params );
62 if ( isset( $params[
'server'] ) ) {
63 $this->server = $params[
'server'];
64 } elseif ( isset( $params[
'cluster'] ) && is_string( $params[
'cluster'] ) ) {
65 $this->cluster = $params[
'cluster'];
70 return [
'random',
'timestamp',
'fifo' ];
86 $found =
$dbr->selectField(
87 'job',
'1', [
'job_cmd' => $this->type,
'job_token' =>
'' ], __METHOD__
103 $size = $this->wanCache->get( $key );
104 if ( is_int( $size ) ) {
112 $size = (int)
$dbr->selectField(
'job',
'COUNT(*)',
113 [
'job_cmd' => $this->type,
'job_token' =>
'' ],
119 $this->wanCache->set( $key, $size, self::CACHE_TTL_SHORT );
129 if ( $this->claimTTL <= 0 ) {
135 $count = $this->wanCache->get( $key );
136 if ( is_int( $count ) ) {
144 $count = (int)
$dbr->selectField(
'job',
'COUNT(*)',
145 [
'job_cmd' => $this->type,
"job_token != {$dbr->addQuotes( '' )}" ],
151 $this->wanCache->set( $key, $count, self::CACHE_TTL_SHORT );
162 if ( $this->claimTTL <= 0 ) {
168 $count = $this->wanCache->get( $key );
169 if ( is_int( $count ) ) {
177 $count = (int)
$dbr->selectField(
'job',
'COUNT(*)',
179 'job_cmd' => $this->type,
180 "job_token != {$dbr->addQuotes( '' )}",
181 "job_attempts >= " .
$dbr->addQuotes( $this->maxTries )
189 $this->wanCache->set( $key, $count, self::CACHE_TTL_SHORT );
215 $dbw->onTransactionPreCommitOrIdle(
216 function (
IDatabase $dbw ) use ( $jobs, $flags, $fname ) {
235 if ( $jobs === [] ) {
241 foreach ( $jobs as
$job ) {
243 if (
$job->ignoreDuplicates() ) {
244 $rowSet[$row[
'job_sha1']] = $row;
250 if ( $flags & self::QOS_ATOMIC ) {
255 if ( count( $rowSet ) ) {
259 'job_sha1' => array_keys( $rowSet ),
264 foreach (
$res as $row ) {
265 wfDebug(
"Job with hash '{$row->job_sha1}' is a duplicate.\n" );
266 unset( $rowSet[$row->job_sha1] );
270 $rows = array_merge( $rowList, array_values( $rowSet ) );
272 foreach ( array_chunk( $rows, 50 ) as $rowBatch ) {
273 $dbw->
insert(
'job', $rowBatch, $method );
275 $this->
incrStats(
'inserts', $this->type, count( $rows ) );
276 $this->
incrStats(
'dupe_inserts', $this->type,
277 count( $rowSet ) + count( $rowList ) - count( $rows )
282 if ( $flags & self::QOS_ATOMIC ) {
301 if ( in_array( $this->order, [
'fifo',
'timestamp' ] ) ) {
304 $rand = mt_rand( 0, self::MAX_JOB_RANDOM );
305 $gte = (bool)mt_rand( 0, 1 );
319 if ( !
$job || mt_rand( 0, 9 ) == 0 ) {
344 $tinyQueue = $this->wanCache->get( $this->
getCacheKey(
'small' ) );
346 $invertedDirection =
false;
355 $ineq = $gte ?
'>=' :
'<=';
356 $dir = $gte ?
'ASC' :
'DESC';
357 $row = $dbw->selectRow(
'job', self::selectFields(),
359 'job_cmd' => $this->type,
361 "job_random {$ineq} {$dbw->addQuotes( $rand )}" ],
363 [
'ORDER BY' =>
"job_random {$dir}" ]
365 if ( !$row && !$invertedDirection ) {
367 $invertedDirection =
true;
374 $row = $dbw->selectRow(
'job', self::selectFields(),
376 'job_cmd' => $this->type,
380 [
'OFFSET' => mt_rand( 0, self::MAX_OFFSET ) ]
384 $this->wanCache->set( $this->
getCacheKey(
'small' ), 1, 30 );
392 'job_token' => $uuid,
393 'job_token_timestamp' => $dbw->timestamp(),
394 'job_attempts = job_attempts+1' ],
395 [
'job_cmd' =>
$this->type,
'job_id' => $row->job_id,
'job_token' =>
'' ],
400 if ( !$dbw->affectedRows() ) {
424 if ( $dbw->getType() ===
'mysql' ) {
429 $dbw->query(
"UPDATE {$dbw->tableName( 'job' )} " .
431 "job_token = {$dbw->addQuotes( $uuid ) }, " .
432 "job_token_timestamp = {$dbw->addQuotes( $dbw->timestamp() )}, " .
433 "job_attempts = job_attempts+1 " .
435 "job_cmd = {$dbw->addQuotes( $this->type )} " .
436 "AND job_token = {$dbw->addQuotes( '' )} " .
437 ") ORDER BY job_id ASC LIMIT 1",
445 'job_token' => $uuid,
446 'job_token_timestamp' => $dbw->timestamp(),
447 'job_attempts = job_attempts+1' ],
449 $dbw->selectSQLText(
'job',
'job_id',
450 [
'job_cmd' => $this->type,
'job_token' =>
'' ],
452 [
'ORDER BY' =>
'job_id ASC',
'LIMIT' => 1 ] ) .
459 if ( $dbw->affectedRows() ) {
460 $row = $dbw->selectRow(
'job', self::selectFields(),
461 [
'job_cmd' => $this->type,
'job_token' => $uuid ], __METHOD__
464 wfDebug(
"Row deleted as duplicate by another process.\n" );
480 $id =
$job->getMetadata(
'id' );
481 if ( $id ===
null ) {
482 throw new MWException(
"Job of type '{$job->getType()}' has no ID." );
492 [
'job_cmd' => $this->type,
'job_id' => $id ],
517 $dbw->onTransactionCommitOrIdle(
518 function () use (
$job ) {
519 parent::doDeduplicateRootJob(
$job );
536 $dbw->delete(
'job', [
'job_cmd' => $this->type ] );
549 if ( $this->server ) {
553 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
554 $lbFactory->waitForReplication( [
555 'domain' => $this->domain,
556 'cluster' => is_string( $this->cluster ) ? $this->cluster :
false
564 foreach ( [
'size',
'acquiredcount' ] as
$type ) {
565 $this->wanCache->delete( $this->
getCacheKey( $type ) );
595 $dbr->select(
'job', self::selectFields(), $conds ),
606 if ( $this->server ) {
610 return is_string( $this->cluster )
611 ?
"DBCluster:{$this->cluster}:{$this->domain}"
612 :
"LBFactory:{$this->domain}";
623 $res =
$dbr->select(
'job',
'DISTINCT job_cmd',
624 [
'job_cmd' => $types ], __METHOD__ );
627 foreach (
$res as $row ) {
628 $types[] = $row->job_cmd;
639 $res =
$dbr->select(
'job', [
'job_cmd',
'COUNT(*) AS count' ],
640 [
'job_cmd' => $types ], __METHOD__, [
'GROUP BY' =>
'job_cmd' ] );
643 foreach (
$res as $row ) {
644 $sizes[$row->job_cmd] = (int)$row->count;
663 if ( !$dbw->lock(
"jobqueue-recycle-{$this->type}", __METHOD__, 1 ) ) {
668 if ( $this->claimTTL > 0 ) {
669 $claimCutoff = $dbw->timestamp( $now - $this->claimTTL );
673 $res = $dbw->select(
'job',
'job_id',
675 'job_cmd' => $this->type,
676 "job_token != {$dbw->addQuotes( '' )}",
677 "job_token_timestamp < {$dbw->addQuotes( $claimCutoff )}",
678 "job_attempts < {$dbw->addQuotes( $this->maxTries )}" ],
684 }, iterator_to_array(
$res )
686 if ( count( $ids ) ) {
693 'job_token_timestamp' => $dbw->timestamp( $now )
695 [
'job_id' => $ids,
"job_token != ''" ],
698 $affected = $dbw->affectedRows();
700 $this->
incrStats(
'recycles', $this->type, $affected );
705 $pruneCutoff = $dbw->timestamp( $now - self::MAX_AGE_PRUNE );
708 "job_token != {$dbw->addQuotes( '' )}",
709 "job_token_timestamp < {$dbw->addQuotes( $pruneCutoff )}"
711 if ( $this->claimTTL > 0 ) {
712 $conds[] =
"job_attempts >= {$dbw->addQuotes( $this->maxTries )}";
716 $res = $dbw->select(
'job',
'job_id', $conds, __METHOD__ );
720 }, iterator_to_array(
$res )
722 if ( count( $ids ) ) {
723 $dbw->delete(
'job', [
'job_id' => $ids ], __METHOD__ );
724 $affected = $dbw->affectedRows();
726 $this->
incrStats(
'abandons', $this->type, $affected );
729 $dbw->unlock(
"jobqueue-recycle-{$this->type}", __METHOD__ );
745 'job_cmd' =>
$job->getType(),
747 'job_title' =>
$job->getParams()[
'title'] ??
'',
751 'job_sha1' => Wikimedia\base_convert(
755 'job_random' => mt_rand( 0, self::MAX_JOB_RANDOM )
787 protected function getDB( $index ) {
788 if ( $this->server ) {
789 if ( $this->conn instanceof
IDatabase ) {
791 } elseif ( $this->conn instanceof
DBError ) {
796 $this->conn = Database::factory( $this->server[
'type'], $this->server );
804 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
805 $lb = is_string( $this->cluster )
806 ? $lbFactory->getExternalLB( $this->cluster )
807 : $lbFactory->getMainLB( $this->domain );
809 if ( $lb->getServerType( $lb->getWriterIndex() ) !==
'sqlite' ) {
812 $flags = $lb::CONN_TRX_AUTOCOMMIT;
818 return $lb->getMaintenanceConnectionRef( $index, [], $this->domain, $flags );
830 return new ScopedCallback(
function () use ( $db, $autoTrx ) {
842 $cluster = is_string( $this->cluster ) ? $this->cluster :
'main';
844 return $this->wanCache->makeGlobalKey(
858 if ( $params !==
false ) {
870 $params = ( (string)$row->job_params !==
'' ) ?
unserialize( $row->job_params ) : [];
871 if ( !is_array( $params ) ) {
872 throw new UnexpectedValueException(
873 "Could not unserialize job with ID '{$row->job_id}'." );
876 $params += [
'namespace' => $row->job_namespace,
'title' => $row->job_title ];
878 $job->setMetadata(
'id', $row->job_id );
879 $job->setMetadata(
'timestamp', $row->job_timestamp );
889 return new JobQueueError( get_class( $e ) .
": " . $e->getMessage() );
908 'job_token_timestamp',