function __construct( Title $title, array $params ) {
parent::__construct( 'refreshLinks', $title, $params );
- // A separate type is used just for cascade-protected backlinks
- if ( !empty( $this->params['prioritize'] ) ) {
- $this->command .= 'Prioritized';
- }
// Base backlink update jobs and per-title update jobs can be de-duplicated.
// If template A changes twice before any jobs run, a clean queue will have:
// (A base, A base)
&& ( !isset( $params['pages'] ) || count( $params['pages'] ) == 1 );
}
+ /**
+ * @param Title $title
+ * @param array $params
+ * @return RefreshLinksJob
+ */
+ public static function newPrioritized( Title $title, array $params ) {
+ $job = new self( $title, $params );
+ $job->command = 'refreshLinksPrioritized';
+
+ return $job;
+ }
+
+ /**
+ * @param Title $title
+ * @param array $params
+ * @return RefreshLinksJob
+ */
+ public static function newDynamic( Title $title, array $params ) {
+ $job = new self( $title, $params );
+ $job->command = 'refreshLinksDynamic';
+
+ return $job;
+ }
+
function run() {
global $wgUpdateRowsPerJob;
JobQueueGroup::singleton()->push( $jobs );
// Job to update link tables for a set of titles
} elseif ( isset( $this->params['pages'] ) ) {
+ $this->waitForMasterPosition();
foreach ( $this->params['pages'] as $pageId => $nsAndKey ) {
list( $ns, $dbKey ) = $nsAndKey;
$this->runForTitle( Title::makeTitleSafe( $ns, $dbKey ) );
}
// Job to update link tables for a given title
} else {
+ $this->waitForMasterPosition();
$this->runForTitle( $this->title );
}
return true;
}
+ protected function waitForMasterPosition() {
+ if ( !empty( $this->params['masterPos'] ) && wfGetLB()->getServerCount() > 1 ) {
+ // Wait for the current/next slave DB handle to catch up to the master.
+ // This way, we get the correct page_latest for templates or files that just
+ // changed milliseconds ago, having triggered this job to begin with.
+ wfGetLB()->waitFor( $this->params['masterPos'] );
+ }
+ }
+
/**
* @param Title $title
* @return bool
*/
- protected function runForTitle( Title $title = null ) {
- if ( is_null( $title ) ) {
- $this->setLastError( "refreshLinks: Invalid title" );
- return false;
- }
-
- // Wait for the DB of the current/next slave DB handle to catch up to the master.
- // This way, we get the correct page_latest for templates or files that just changed
- // milliseconds ago, having triggered this job to begin with.
- if ( isset( $this->params['masterPos'] ) && $this->params['masterPos'] !== false ) {
- wfGetLB()->waitFor( $this->params['masterPos'] );
+ protected function runForTitle( Title $title ) {
+ $page = WikiPage::factory( $title );
+ if ( !empty( $this->params['triggeringRevisionId'] ) ) {
+ // Fetch the specified revision; lockAndGetLatest() below detects if the page
+ // was edited since and aborts in order to avoid corrupting the link tables
+ $revision = Revision::newFromId(
+ $this->params['triggeringRevisionId'],
+ Revision::READ_LATEST
+ );
+ } else {
+ // Fetch current revision; READ_LATEST reduces lockAndGetLatest() check failures
+ $revision = Revision::newFromTitle( $title, false, Revision::READ_LATEST );
}
- // Clear out title cache data from prior job transaction snapshots
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
-
- // Fetch the current page and revision...
- $page = WikiPage::factory( $title );
- $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
if ( !$revision ) {
- $this->setLastError( "refreshLinks: Article not found {$title->getPrefixedDBkey()}" );
- return false; // XXX: what if it was just deleted?
+ $this->setLastError( "Revision not found for {$title->getPrefixedDBkey()}" );
+ return false; // just deleted?
}
$content = $revision->getContent( Revision::RAW );
}
$updates = $content->getSecondaryDataUpdates(
- $title, null, !empty( $this->params['useRecursiveLinksUpdate'] ), $parserOutput );
+ $title,
+ null,
+ !empty( $this->params['useRecursiveLinksUpdate'] ),
+ $parserOutput
+ );
+
foreach ( $updates as $key => $update ) {
- if ( $update instanceof LinksUpdate && isset( $this->params['triggeredRecursive'] ) ) {
- $update->setTriggeredRecursive();
+ // FIXME: move category change RC stuff to a separate update.
+ // RC entry addition aborts if edits where since made, which is not necessary.
+ // It's also an SoC violation for links update code to care about RC.
+ if ( $update instanceof LinksUpdate ) {
+ if ( !empty( $this->params['triggeredRecursive'] ) ) {
+ $update->setTriggeredRecursive();
+ }
+ if ( !empty( $this->params['triggeringUser'] ) ) {
+ $userInfo = $this->params['triggeringUser'];
+ if ( $userInfo['userId'] ) {
+ $user = User::newFromId( $userInfo['userId'] );
+ } else {
+ // Anonymous, use the username
+ $user = User::newFromName( $userInfo['userName'], false );
+ }
+ $update->setTriggeringUser( $user );
+ }
+ if ( !empty( $this->params['triggeringRevisionId'] ) ) {
+ $update->setRevision( $revision );
+ }
}
}
+ $latestNow = $page->lockAndGetLatest();
+ if ( !$latestNow || $revision->getId() != $latestNow ) {
+ // Do not clobber over newer updates with older ones. If all jobs where FIFO and
+ // serialized, it would be OK to update links based on older revisions since it
+ // would eventually get to the latest. Since that is not the case (by design),
+ // only update the link tables to a state matching the current revision's output.
+ $this->setLastError( "page_latest changed from {$revision->getId()} to $latestNow" );
+ return false;
+ }
+
DataUpdate::runUpdates( $updates );
InfoAction::invalidateCache( $title );