Make CDN purge calls use DeferredUpdates
[lhc/web/wiklou.git] / includes / jobqueue / jobs / RefreshLinksJob.php
index 1d59b32..fa3278d 100644 (file)
@@ -41,10 +41,6 @@ class RefreshLinksJob extends Job {
 
        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)
@@ -64,6 +60,30 @@ class RefreshLinksJob extends Job {
                        && ( !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;
 
@@ -95,45 +115,50 @@ class RefreshLinksJob extends Job {
                        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 );
@@ -198,13 +223,46 @@ class RefreshLinksJob extends Job {
                }
 
                $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 );