'GenerateNormalizerDataMl' => __DIR__ . '/maintenance/language/generateNormalizerDataMl.php',
'GenerateSitemap' => __DIR__ . '/maintenance/generateSitemap.php',
'GenericArrayObject' => __DIR__ . '/includes/libs/GenericArrayObject.php',
+ 'GenericParameterJob' => __DIR__ . '/includes/jobqueue/GenericParameterJob.php',
'GetConfiguration' => __DIR__ . '/maintenance/getConfiguration.php',
'GetLagTimes' => __DIR__ . '/maintenance/getLagTimes.php',
'GetReplicaServer' => __DIR__ . '/maintenance/getReplicaServer.php',
'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php',
'RunBatchedQuery' => __DIR__ . '/maintenance/runBatchedQuery.php',
'RunJobs' => __DIR__ . '/maintenance/runJobs.php',
+ 'RunnableJob' => __DIR__ . '/includes/jobqueue/RunnableJob.php',
'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SamplingStatsdClient' => __DIR__ . '/includes/libs/stats/SamplingStatsdClient.php',
self::purge( $this->urls );
if ( $wgCdnReboundPurgeDelay > 0 ) {
- JobQueueGroup::singleton()->lazyPush( new CdnPurgeJob(
- Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __CLASS__ ),
- [
- 'urls' => $this->urls,
- 'jobReleaseTimestamp' => time() + $wgCdnReboundPurgeDelay
- ]
- ) );
+ JobQueueGroup::singleton()->lazyPush( new CdnPurgeJob( [
+ 'urls' => $this->urls,
+ 'jobReleaseTimestamp' => time() + $wgCdnReboundPurgeDelay
+ ] ) );
}
}
--- /dev/null
+<?php
+/**
+ * Interface for generic jobs only uses the parameters field.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Interface for generic jobs only uses the parameters field and are JSON serializable
+ *
+ * @ingroup JobQueue
+ * @since 1.33
+ */
+interface GenericParameterJob extends IJobSpecification {
+ /**
+ * @param array $params JSON-serializable map of parameters
+ */
+ public function __construct( array $params );
+}
<?php
/**
- * Job queue task description base code.
+ * Job queue task description interface
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*/
/**
- * Job queue task description interface
+ * Interface for serializable objects that describe a job queue task
+ *
+ * A job specification can be inserted into a queue via JobQueue::push().
+ * The specification parameters should be JSON serializable (e.g. no PHP classes).
+ * Whatever queue the job specification is pushed into is assumed to have job runners
+ * that will eventually pop the job specification from the queue, construct a RunnableJob
+ * instance from the specification, and then execute that instance via RunnableJob::run().
*
* @ingroup JobQueue
* @since 1.23
*/
interface IJobSpecification {
/**
- * @return string Job type
+ * @return string Job type that defines what sort of changes this job makes
*/
public function getType();
/**
- * @return array
+ * @return array Parameters that specify sources, targets, and options for execution
*/
public function getParams();
* @return bool Whether this is job is a root job
*/
public function isRootJob();
-
- /**
- * @return Title Descriptive title (this can simply be informative)
- */
- public function getTitle();
}
*
* @ingroup JobQueue
*/
-abstract class Job implements IJobSpecification {
+abstract class Job implements RunnableJob {
/** @var string */
public $command;
/** @var int Job must not be wrapped in the usual explicit LBFactory transaction round */
const JOB_NO_EXPLICIT_TRX_ROUND = 1;
- /**
- * Run the job
- * @return bool Success
- */
- abstract public function run();
-
/**
* Create the appropriate object to handle a specific job
*
$title = $params;
$params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
} else {
- // Subclasses can override getTitle() to return something more meaningful
- $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+ $title = ( isset( $params['namespace'] ) && isset( $params['title'] ) )
+ ? Title::makeTitle( $params['namespace'], $params['title'] )
+ : Title::makeTitle( NS_SPECIAL, '' );
}
+ $params = is_array( $params ) ? $params : []; // sanity
+
if ( isset( $wgJobClasses[$command] ) ) {
$handler = $wgJobClasses[$command];
if ( is_callable( $handler ) ) {
$job = call_user_func( $handler, $title, $params );
} elseif ( class_exists( $handler ) ) {
- $job = new $handler( $title, $params );
+ if ( is_subclass_of( $handler, GenericParameterJob::class ) ) {
+ $job = new $handler( $params );
+ } else {
+ $job = new $handler( $title, $params );
+ }
} else {
$job = null;
}
if ( $params instanceof Title ) {
// Backwards compatibility for old signature ($command, $title, $params)
$title = $params;
- $params = func_get_arg( 2 );
- } else {
- // Subclasses can override getTitle() to return something more meaningful
- $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+ $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
+ $params = is_array( $params ) ? $params : []; // sanity
+ // Set namespace/title params if both are missing and this is not a dummy title
+ if (
+ $title->getDBkey() !== '' &&
+ !isset( $params['namespace'] ) &&
+ !isset( $params['title'] )
+ ) {
+ $params['namespace'] = $title->getNamespace();
+ $params['title'] = $title->getDBKey();
+ // Note that JobQueue classes will prefer the parameters over getTitle()
+ $this->title = $title;
+ }
}
$this->command = $command;
- $this->title = $title;
- $this->params = is_array( $params ) ? $params : [];
- if ( !isset( $this->params['requestId'] ) ) {
- $this->params['requestId'] = WebRequest::getRequestId();
+ $this->params = $params + [ 'requestId' => WebRequest::getRequestId() ];
+ if ( $this->title === null ) {
+ $this->title = ( isset( $params['namespace'] ) && isset( $params['title'] ) )
+ ? Title::makeTitle( $params['namespace'], $params['title'] )
+ : Title::makeTitle( NS_SPECIAL, '' );
}
}
/**
* @return Title
*/
- public function getTitle() {
+ final public function getTitle() {
return $this->title;
}
public function getDeduplicationInfo() {
$info = [
'type' => $this->getType(),
- 'namespace' => $this->getTitle()->getNamespace(),
- 'title' => $this->getTitle()->getDBkey(),
'params' => $this->getParams()
];
if ( is_array( $info['params'] ) ) {
* Outside callers should use JobQueueGroup::pop() instead of this function.
*
* @throws JobQueueError
- * @return Job|bool Returns false if there are no jobs
+ * @return RunnableJob|bool Returns false if there are no jobs
*/
final public function pop() {
$this->assertNotReadOnly();
/**
* @see JobQueue::pop()
- * @return Job|bool
+ * @return RunnableJob|bool
*/
abstract protected function doPop();
* This does nothing for certain queue classes or if "claimTTL" is not set.
* Outside callers should use JobQueueGroup::ack() instead of this function.
*
- * @param Job $job
+ * @param RunnableJob $job
* @return void
* @throws JobQueueError
*/
- final public function ack( Job $job ) {
+ final public function ack( RunnableJob $job ) {
$this->assertNotReadOnly();
if ( $job->getType() !== $this->type ) {
throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
/**
* @see JobQueue::ack()
- * @param Job $job
+ * @param RunnableJob $job
*/
- abstract protected function doAck( Job $job );
+ abstract protected function doAck( RunnableJob $job );
/**
* Register the "root job" of a given job into the queue for de-duplication.
/**
* Check if the "root" job of a given job has been superseded by a newer one
*
- * @param Job $job
+ * @param IJobSpecification $job
* @throws JobQueueError
* @return bool
*/
- final protected function isRootJobOldDuplicate( Job $job ) {
+ final protected function isRootJobOldDuplicate( IJobSpecification $job ) {
if ( $job->getType() !== $this->type ) {
throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
/**
* @see JobQueue::isRootJobOldDuplicate()
- * @param Job $job
+ * @param IJobSpecification $job
* @return bool
*/
- protected function doIsRootJobOldDuplicate( Job $job ) {
+ protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
if ( !$job->hasRootJobParams() ) {
return false; // job has no de-deplication info
}
return null; // not supported
}
+ /**
+ * @param string $command
+ * @param array $params
+ * @return Job
+ */
+ protected function factoryJob( $command, $params ) {
+ // @TODO: dependency inject this as a callback
+ return Job::factory( $command, $params );
+ }
+
/**
* @throws JobQueueReadOnlyError
*/
/**
* @see JobQueue::doPop()
- * @return Job|bool
+ * @return RunnableJob|bool
*/
protected function doPop() {
$dbw = $this->getMasterDB();
break; // nothing to do
}
$this->incrStats( 'pops', $this->type );
+
// Get the job object from the row...
- $title = Title::makeTitle( $row->job_namespace, $row->job_title );
- $job = Job::factory( $row->job_cmd, $title,
- self::extractBlob( $row->job_params ) );
+ $params = self::extractBlob( $row->job_params );
+ $params = is_array( $params ) ? $params : []; // sanity
+ $params += [ 'namespace' => $row->job_namespace, 'title' => $row->job_title ];
+ $job = $this->factoryJob( $row->job_cmd, $params );
$job->setMetadata( 'id', $row->job_id );
$job->setMetadata( 'timestamp', $row->job_timestamp );
break; // done
/**
* @see JobQueue::doAck()
- * @param Job $job
+ * @param RunnableJob $job
* @throws MWException
*/
- protected function doAck( Job $job ) {
+ protected function doAck( RunnableJob $job ) {
$id = $job->getMetadata( 'id' );
if ( $id === null ) {
throw new MWException( "Job of type '{$job->getType()}' has no ID." );
return new MappedIterator(
$dbr->select( 'job', self::selectFields(), $conds ),
function ( $row ) {
- $job = Job::factory(
- $row->job_cmd,
- Title::makeTitle( $row->job_namespace, $row->job_title ),
- strlen( $row->job_params ) ? unserialize( $row->job_params ) : []
- );
+ $params = strlen( $row->job_params ) ? unserialize( $row->job_params ) : [];
+ $params = is_array( $params ) ? $params : []; // sanity
+ $params += [
+ 'namespace' => $row->job_namespace,
+ 'title' => $row->job_title
+ ];
+
+ $job = $this->factoryJob( $row->job_cmd, $params );
$job->setMetadata( 'id', $row->job_id );
$job->setMetadata( 'timestamp', $row->job_timestamp );
return [
// Fields that describe the nature of the job
'job_cmd' => $job->getType(),
- 'job_namespace' => $job->getTitle()->getNamespace(),
- 'job_title' => $job->getTitle()->getDBkey(),
+ 'job_namespace' => $job->getParams()['namespace'] ?? NS_SPECIAL,
+ 'job_title' => $job->getParams()['title'] ?? '',
'job_params' => self::makeBlob( $job->getParams() ),
// Additional job metadata
'job_timestamp' => $db->timestamp(),
* @param HashRing &$partitionRing
* @param int $flags
* @throws JobQueueError
- * @return array List of Job object that could not be inserted
+ * @return IJobSpecification[] List of Job object that could not be inserted
*/
protected function tryJobInsertions( array $jobs, HashRing &$partitionRing, $flags ) {
$jobsLeft = [];
return false;
}
- protected function doAck( Job $job ) {
+ protected function doAck( RunnableJob $job ) {
$partition = $job->getMetadata( 'QueuePartition' );
if ( $partition === null ) {
throw new MWException( "The given job has no defined partition name." );
$this->partitionQueues[$partition]->ack( $job );
}
- protected function doIsRootJobOldDuplicate( Job $job ) {
+ protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
$signature = $job->getRootJobParams()['rootJobSignature'];
$partition = $this->partitionRing->getLiveLocation( $signature );
try {
* @param int|string $qtype JobQueueGroup::TYPE_* constant or job type string
* @param int $flags Bitfield of JobQueueGroup::USE_* constants
* @param array $blacklist List of job types to ignore
- * @return Job|bool Returns false on failure
+ * @return RunnableJob|bool Returns false on failure
*/
public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0, array $blacklist = [] ) {
global $wgJobClasses;
/**
* @see JobQueue::doPop
*
- * @return Job|bool
+ * @return RunnableJob|bool
*/
protected function doPop() {
if ( $this->doGetSize() == 0 ) {
/**
* @see JobQueue::doAck
*
- * @param Job $job
+ * @param RunnableJob $job
*/
- protected function doAck( Job $job ) {
+ protected function doAck( RunnableJob $job ) {
if ( $this->getAcquiredCount() == 0 ) {
return;
}
/**
* @param IJobSpecification $spec
- *
- * @return Job
+ * @return RunnableJob
*/
public function jobFromSpecInternal( IJobSpecification $spec ) {
- return Job::factory( $spec->getType(), $spec->getTitle(), $spec->getParams() );
+ return $this->factoryJob( $spec->getType(), $spec->getParams() );
}
/**
/**
* @see JobQueue::doPop()
- * @return Job|bool
+ * @return RunnableJob|bool
* @throws JobQueueError
*/
protected function doPop() {
/**
* @see JobQueue::doAck()
- * @param Job $job
- * @return Job|bool
+ * @param RunnableJob $job
+ * @return RunnableJob|bool
* @throws UnexpectedValueException
* @throws JobQueueError
*/
- protected function doAck( Job $job ) {
+ protected function doAck( RunnableJob $job ) {
$uuid = $job->getMetadata( 'uuid' );
if ( $uuid === null ) {
throw new UnexpectedValueException( "Job of type '{$job->getType()}' has no UUID." );
/**
* @see JobQueue::doIsRootJobOldDuplicate()
- * @param Job $job
+ * @param IJobSpecification $job
* @return bool
* @throws JobQueueError
*/
- protected function doIsRootJobOldDuplicate( Job $job ) {
+ protected function doIsRootJobOldDuplicate( IJobSpecification $job ) {
if ( !$job->hasRootJobParams() ) {
return false; // job has no de-deplication info
}
*
* @param string $uid
* @param RedisConnRef $conn
- * @return Job|bool Returns false if the job does not exist
+ * @return RunnableJob|bool Returns false if the job does not exist
* @throws JobQueueError
* @throws UnexpectedValueException
*/
if ( !is_array( $item ) ) { // this shouldn't happen
throw new UnexpectedValueException( "Could not find job with ID '$uid'." );
}
- $title = Title::makeTitle( $item['namespace'], $item['title'] );
- $job = Job::factory( $item['type'], $title, $item['params'] );
+
+ $params = $item['params'];
+ $params += [ 'namespace' => $item['namespace'], 'title' => $item['title'] ];
+ $job = $this->factoryJob( $item['type'], $params );
$job->setMetadata( 'uuid', $item['uuid'] );
$job->setMetadata( 'timestamp', $item['timestamp'] );
// Add in attempt count for debugging at showJobs.php
return [
// Fields that describe the nature of the job
'type' => $job->getType(),
- 'namespace' => $job->getTitle()->getNamespace(),
- 'title' => $job->getTitle()->getDBkey(),
+ 'namespace' => $job->getParams()['namespace'] ?? NS_SPECIAL,
+ 'title' => $job->getParams()['title'] ?? '',
'params' => $job->getParams(),
// Some jobs cannot run until a "release timestamp"
'rtimestamp' => $job->getReleaseTimestamp() ?: 0,
/**
* @param array $fields
- * @return Job|bool
+ * @return RunnableJob|bool
*/
protected function getJobFromFields( array $fields ) {
- $title = Title::makeTitle( $fields['namespace'], $fields['title'] );
- $job = Job::factory( $fields['type'], $title, $fields['params'] );
+ $params = $fields['params'];
+ $params += [ 'namespace' => $fields['namespace'], 'title' => $fields['title'] ];
+
+ $job = $this->factoryJob( $fields['type'], $params );
$job->setMetadata( 'uuid', $fields['uuid'] );
$job->setMetadata( 'timestamp', $fields['timestamp'] );
* $job = new JobSpecification(
* 'null',
* array( 'lives' => 1, 'usleep' => 100, 'pi' => 3.141569 ),
- * array( 'removeDuplicates' => 1 ),
- * Title::makeTitle( NS_SPECIAL, 'nullity' )
+ * array( 'removeDuplicates' => 1 )
* );
* JobQueueGroup::singleton()->push( $job )
* @endcode
$this->validateParams( $opts );
$this->type = $type;
+ if ( $title instanceof Title ) {
+ // Make sure JobQueue classes can pull the title from parameters alone
+ if ( $title->getDBkey() !== '' ) {
+ $params += [
+ 'namespace' => $title->getNamespace(),
+ 'title' => $title->getDBkey()
+ ];
+ }
+ } else {
+ $title = Title::makeTitle( NS_SPECIAL, '' );
+ }
$this->params = $params;
- $this->title = $title ?: Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+ $this->title = $title;
$this->opts = $opts;
}
--- /dev/null
+<?php
+/**
+ * Job queue task instance that can be executed via a run() method
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Job that has a run() method and metadata accessors for JobQueue::pop() and JobQueue::ack()
+ *
+ * Instances are not only enqueueable via JobQueue::push(), but they can also be executed by
+ * by calling their run() method. When constructing a job to be enqueued via JobQueue::push(),
+ * it will not be possible to construct a RunnableJob instance if the class for that job is not
+ * loaded by the application for the local DB domain. In that case, the general-purpose
+ * JobSpecification class can be used instead.
+ *
+ * @ingroup JobQueue
+ * @since 1.33
+ */
+interface RunnableJob extends IJobSpecification {
+ /**
+ * Run the job
+ * @return bool Success
+ */
+ public function run();
+
+ /**
+ * @param string|null $field Metadata field or null to get all the metadata
+ * @return mixed|null Value; null if missing
+ */
+ public function getMetadata( $field = null );
+
+ /**
+ * @param string $field Key name to set the value for
+ * @param mixed $value The value to set the field for
+ * @return mixed|null The prior field value; null if missing
+ */
+ public function setMetadata( $field, $value );
+}
* @ingroup JobQueue
* @since 1.27
*/
-class CdnPurgeJob extends Job {
- /**
- * @param Title $title
- * @param array $params Job parameters (urls)
- */
- function __construct( Title $title, array $params ) {
- parent::__construct( 'cdnPurge', $title, $params );
+class CdnPurgeJob extends Job implements GenericParameterJob {
+ function __construct( array $params ) {
+ parent::__construct( 'cdnPurge', $params );
$this->removeDuplicates = false; // delay semantics are critical
}
* @ingroup JobQueue
* @since 1.31
*/
-class ClearUserWatchlistJob extends Job {
+class ClearUserWatchlistJob extends Job implements GenericParameterJob {
+ /**
+ * @param array $params
+ * - userId, The ID for the user whose watchlist is being cleared.
+ * - maxWatchlistId, The maximum wl_id at the time the job was first created,
+ */
+ public function __construct( array $params ) {
+ parent::__construct( 'clearUserWatchlist', $params );
+
+ $this->removeDuplicates = true;
+ }
/**
* @param User $user User to clear the watchlist for.
* @return ClearUserWatchlistJob
*/
public static function newForUser( User $user, $maxWatchlistId ) {
- return new self(
- null,
- [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ]
- );
- }
-
- /**
- * @param Title|null $title Not used by this job.
- * @param array $params
- * - userId, The ID for the user whose watchlist is being cleared.
- * - maxWatchlistId, The maximum wl_id at the time the job was first created,
- */
- public function __construct( Title $title = null, array $params ) {
- parent::__construct(
- 'clearUserWatchlist',
- SpecialPage::getTitleFor( 'EditWatchlist', 'clear' ),
- $params
- );
-
- $this->removeDuplicates = true;
+ return new self( [ 'userId' => $user->getId(), 'maxWatchlistId' => $maxWatchlistId ] );
}
public function run() {
if ( count( $watchlistIds ) === (int)$batchSize ) {
// Until we get less results than the limit, recursively push
// the same job again.
- JobQueueGroup::singleton()->push( new self( $this->getTitle(), $this->getParams() ) );
+ JobQueueGroup::singleton()->push( new self( $this->getParams() ) );
}
return true;
* @ingroup JobQueue
* @since 1.31
*/
-class ClearWatchlistNotificationsJob extends Job {
- function __construct( Title $title, array $params ) {
- parent::__construct( 'clearWatchlistNotifications', $title, $params );
+class ClearWatchlistNotificationsJob extends Job implements GenericParameterJob {
+ function __construct( array $params ) {
+ parent::__construct( 'clearWatchlistNotifications', $params );
static $required = [ 'userId', 'casTime' ];
$missing = implode( ', ', array_diff( $required, array_keys( $this->params ) ) );
/**
* Class DeletePageJob
*/
-class DeletePageJob extends Job {
- public function __construct( $title, $params = [] ) {
- parent::__construct( 'deletePage', $title, $params );
+class DeletePageJob extends Job implements GenericParameterJob {
+ public function __construct( array $params ) {
+ parent::__construct( 'deletePage', $params );
+
+ $this->title = Title::makeTitle( $params['namespace'], $params['title'] );
}
- /**
- * Execute the job
- *
- * @return bool
- */
public function run() {
// Failure to load the page is not job failure.
// A parallel deletion operation may have already completed the page deletion.
*
* @ingroup JobQueue
*/
-final class DuplicateJob extends Job {
+final class DuplicateJob extends Job implements GenericParameterJob {
/**
* Callers should use DuplicateJob::newFromJob() instead
*
- * @param Title $title
* @param array $params Job parameters
*/
- function __construct( Title $title, array $params ) {
- parent::__construct( 'duplicate', $title, $params );
+ function __construct( array $params ) {
+ parent::__construct( 'duplicate', $params );
}
/**
* Get a duplicate no-op version of a job
*
- * @param Job $job
+ * @param RunnableJob $job
* @return Job
*/
- public static function newFromJob( Job $job ) {
- $djob = new self( $job->getTitle(), $job->getParams() );
+ public static function newFromJob( RunnableJob $job ) {
+ $djob = new self( $job->getParams() );
$djob->command = $job->getType();
$djob->params = is_array( $djob->params ) ? $djob->params : [];
$djob->params = [ 'isDuplicate' => true ] + $djob->params;
- $djob->metadata = $job->metadata;
+ $djob->metadata = $job->getMetadata();
return $djob;
}
* @ingroup JobQueue
* @since 1.25
*/
-final class EnqueueJob extends Job {
+final class EnqueueJob extends Job implements GenericParameterJob {
/**
* Callers should use the factory methods instead
*
- * @param Title $title
* @param array $params Job parameters
*/
- function __construct( Title $title, array $params ) {
- parent::__construct( 'enqueue', $title, $params );
+ public function __construct( array $params ) {
+ parent::__construct( 'enqueue', $params );
}
/**
}
}
- $eJob = new self(
- Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __CLASS__ ),
- [ 'jobsByDomain' => $jobMapsByDomain ]
- );
+ $eJob = new self( [ 'jobsByDomain' => $jobMapsByDomain ] );
// If *all* jobs to be pushed are to be de-duplicated (a common case), then
// de-duplicate this whole job itself to avoid build up in high traffic cases
$eJob->removeDuplicates = $deduplicate;
* @code
* $ php maintenance/eval.php
* > $queue = JobQueueGroup::singleton();
- * > $job = new NullJob( Title::newMainPage(), [ 'lives' => 10 ] );
+ * > $job = new NullJob( [ 'lives' => 10 ] );
* > $queue->push( $job );
* @endcode
* You can then confirm the job has been enqueued by using the showJobs.php
*
* @ingroup JobQueue
*/
-class NullJob extends Job {
+class NullJob extends Job implements GenericParameterJob {
/**
- * @param Title $title
* @param array $params Job parameters (lives, usleep)
*/
- function __construct( Title $title, array $params ) {
- parent::__construct( 'null', $title, $params );
+ function __construct( array $params ) {
+ parent::__construct( 'null', $params );
if ( !isset( $this->params['lives'] ) ) {
$this->params['lives'] = 1;
}
if ( $this->params['lives'] > 1 ) {
$params = $this->params;
$params['lives']--;
- $job = new self( $this->title, $params );
+ $job = new self( $params );
JobQueueGroup::singleton()->push( $job );
}
* @ingroup JobQueue
*/
-class UserGroupExpiryJob extends Job {
- public function __construct( $params = [] ) {
- parent::__construct( 'userGroupExpiry', Title::newMainPage(), $params );
+class UserGroupExpiryJob extends Job implements GenericParameterJob {
+ public function __construct( array $params = [] ) {
+ parent::__construct( 'userGroupExpiry', $params );
$this->removeDuplicates = true;
}
$dbw->endAtomic( __METHOD__ );
$jobParams = [
+ 'namespace' => $this->getTitle()->getNamespace(),
+ 'title' => $this->getTitle()->getDBkey(),
'wikiPageId' => $id,
'requestId' => $webRequestId ?? WebRequest::getRequestId(),
'reason' => $reason,
'logsubtype' => $logsubtype,
];
- $job = new DeletePageJob( $this->getTitle(), $jobParams );
+ $job = new DeletePageJob( $jobParams );
JobQueueGroup::singleton()->push( $job );
$status->warning( 'delete-scheduled',
}
// If the page is watched by the user (or may be watched), update the timestamp
- $job = new ClearWatchlistNotificationsJob(
- $user->getUserPage(),
- [ 'userId' => $user->getId(), 'timestamp' => $timestamp, 'casTime' => time() ]
- );
+ $job = new ClearWatchlistNotificationsJob( [
+ 'userId' => $user->getId(), 'timestamp' => $timestamp, 'casTime' => time()
+ ] );
// Try to run this post-send
// Calls DeferredUpdates::addCallableUpdate in normal operation
$this->setService( 'MainWANObjectCache', $cache );
$jobq = JobQueueGroup::singleton();
- $jobq->push( new NullJob( Title::newMainPage(), [] ) );
+ $jobq->push( Job::factory( 'null', Title::newMainPage(), [] ) );
$this->assertEquals( 1, SiteStats::jobs(),
'A single job enqueued bumps jobscount stat to 1' );
- $jobq->push( new NullJob( Title::newMainPage(), [] ) );
+ $jobq->push( Job::factory( 'null', Title::newMainPage(), [] ) );
$this->assertEquals( 1, SiteStats::jobs(),
'SiteStats::jobs() count does not reflect addition ' .
'of a second job (cached)'
}
function newJob( $i = 0, $rootJob = [] ) {
- return new NullJob( Title::newMainPage(),
+ return Job::factory( 'null', Title::newMainPage(),
[ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 0, 'i' => $i ] + $rootJob );
}
function newDedupedJob( $i = 0, $rootJob = [] ) {
- return new NullJob( Title::newMainPage(),
+ return Job::factory( 'null', Title::newMainPage(),
[ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 1, 'i' => $i ] + $rootJob );
}
}
return [
[
$this->getMockJob( false ),
- 'someCommand ' . $requestId
+ 'someCommand Special: ' . $requestId
],
[
$this->getMockJob( [ 'key' => 'val' ] ),
- 'someCommand key=val ' . $requestId
+ 'someCommand Special: key=val ' . $requestId
],
[
$this->getMockJob( [ 'key' => [ 'inkey' => 'inval' ] ] ),
- 'someCommand key={"inkey":"inval"} ' . $requestId
+ 'someCommand Special: key={"inkey":"inval"} ' . $requestId
],
[
$this->getMockJob( [ 'val1' ] ),
- 'someCommand 0=val1 ' . $requestId
+ 'someCommand Special: 0=val1 ' . $requestId
],
[
$this->getMockJob( [ 'val1', 'val2' ] ),
- 'someCommand 0=val1 1=val2 ' . $requestId
+ 'someCommand Special: 0=val1 1=val2 ' . $requestId
],
[
$this->getMockJob( [ new stdClass() ] ),
- 'someCommand 0=object(stdClass) ' . $requestId
+ 'someCommand Special: 0=object(stdClass) ' . $requestId
],
[
$this->getMockJob( [ $mockToStringObj ] ),
- 'someCommand 0={STRING_OBJ_VAL} ' . $requestId
+ 'someCommand Special: 0={STRING_OBJ_VAL} ' . $requestId
],
[
$this->getMockJob( [
],
"triggeredRecursive" => true
] ),
- 'someCommand pages={"932737":[0,"Robert_James_Waller"]} ' .
+ 'someCommand Special: pages={"932737":[0,"Robert_James_Waller"]} ' .
'rootJobSignature=45868e99bba89064e4483743ebb9b682ef95c1a7 ' .
'rootJobTimestamp=20160309110158 masterPos=' .
'{"file":"db1023-bin.001288","pos":"308257743","asOfTime":' .
}
public function getMockJob( $params ) {
+ $title = new Title();
$mock = $this->getMockForAbstractClass(
Job::class,
- [ 'someCommand', new Title(), $params ],
+ [ 'someCommand', $title, $params ],
'SomeJob'
);
+
return $mock;
}
return [
'class name' => [ 'NullJob' ],
'closure' => [ function ( Title $title, array $params ) {
- return new NullJob( $title, $params );
+ return Job::factory( 'null', $title, $params );
} ],
'function' => [ [ $this, 'newNullJob' ] ],
'static function' => [ self::class . '::staticNullJob' ]
}
public function newNullJob( Title $title, array $params ) {
- return new NullJob( $title, $params );
+ return Job::factory( 'null', $title, $params );
}
public static function staticNullJob( Title $title, array $params ) {
- return new NullJob( $title, $params );
+ return Job::factory( 'null', $title, $params );
+ }
+
+ /**
+ * @covers Job::factory
+ * @covers Job::__construct()
+ */
+ public function testJobSignatureGeneric() {
+ $testPage = Title::makeTitle( NS_PROJECT, 'x' );
+ $blankTitle = Title::makeTitle( NS_SPECIAL, '' );
+ $params = [ 'z' => 1, 'lives' => 1, 'usleep' => 0 ];
+ $paramsWithTitle = $params + [ 'namespace' => NS_PROJECT, 'title' => 'x' ];
+
+ $job = new NullJob( [ 'namespace' => NS_PROJECT, 'title' => 'x' ] + $params );
+ $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+ $job = Job::factory( 'null', $testPage, $params );
+ $this->assertEquals( $blankTitle->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $params );
+
+ $job = Job::factory( 'null', $paramsWithTitle );
+ $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+ $job = Job::factory( 'null', $params );
+ $this->assertTrue( $blankTitle->equals( $job->getTitle() ) );
+ $this->assertJobParamsMatch( $job, $params );
+ }
+
+ /**
+ * @covers Job::factory
+ * @covers Job::__construct()
+ */
+ public function testJobSignatureTitleBased() {
+ $testPage = Title::makeTitle( NS_PROJECT, 'x' );
+ $blankTitle = Title::makeTitle( NS_SPECIAL, '' );
+ $params = [ 'z' => 1, 'causeAction' => 'unknown', 'causeAgent' => 'unknown' ];
+ $paramsWithTitle = $params + [ 'namespace' => NS_PROJECT, 'title' => 'x' ];
+
+ $job = new RefreshLinksJob( $testPage, $params );
+ $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertSame( $testPage, $job->getTitle() );
+ $this->assertJobParamsMatch( $job, $paramsWithTitle );
+ $this->assertSame( $testPage, $job->getTitle() );
+
+ $job = Job::factory( 'refreshLinks', $testPage, $params );
+ $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+ $job = Job::factory( 'refreshLinks', $paramsWithTitle );
+ $this->assertEquals( $testPage->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $paramsWithTitle );
+
+ $job = Job::factory( 'refreshLinks', $params );
+ $this->assertTrue( $blankTitle->equals( $job->getTitle() ) );
+ $this->assertJobParamsMatch( $job, $params );
+ }
+
+ /**
+ * @covers Job::factory
+ * @covers Job::__construct()
+ */
+ public function testJobSignatureTitleBasedIncomplete() {
+ $testPage = Title::makeTitle( NS_PROJECT, 'x' );
+ $blankTitle = Title::makeTitle( NS_SPECIAL, '' );
+ $params = [ 'z' => 1, 'causeAction' => 'unknown', 'causeAgent' => 'unknown' ];
+
+ $job = new RefreshLinksJob( $testPage, $params + [ 'namespace' => 0 ] );
+ $this->assertEquals( $blankTitle->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $params + [ 'namespace' => 0 ] );
+
+ $job = new RefreshLinksJob( $testPage, $params + [ 'title' => 'x' ] );
+ $this->assertEquals( $blankTitle->getPrefixedText(), $job->getTitle()->getPrefixedText() );
+ $this->assertJobParamsMatch( $job, $params + [ 'title' => 'x' ] );
}
+ private function assertJobParamsMatch( IJobSpecification $job, array $params ) {
+ $actual = $job->getParams();
+ unset( $actual['requestId'] );
+
+ $this->assertEquals( $actual, $params );
+ }
}
$this->setMwGlobals( 'wgUpdateRowsPerQuery', 2 );
JobQueueGroup::singleton()->push(
- new ClearUserWatchlistJob(
- null,
- [
- 'userId' => $user->getId(),
- 'maxWatchlistId' => $maxId,
- ]
- )
+ new ClearUserWatchlistJob( [
+ 'userId' => $user->getId(), 'maxWatchlistId' => $maxId,
+ ] )
);
$this->assertEquals( 1, JobQueueGroup::singleton()->getQueueSizes()['clearUserWatchlist'] );