Example #1
0
 /**
  * Check that a prepared edit is in cache and still up-to-date
  *
  * This method blocks if the prepared edit is already being rendered,
  * waiting until rendering finishes before doing final validity checks.
  *
  * The cache is rejected if template or file changes are detected.
  * Note that foreign template or file transclusions are not checked.
  *
  * The result is a map (pstContent,output,timestamp) with fields
  * extracted directly from WikiPage::prepareContentForEdit().
  *
  * @param Title $title
  * @param Content $content
  * @param User $user User to get parser options from
  * @return stdClass|bool Returns false on cache miss
  */
 public static function checkCache(Title $title, Content $content, User $user)
 {
     if ($user->isBot()) {
         return false;
         // bots never stash - don't pollute stats
     }
     $cache = ObjectCache::getLocalClusterInstance();
     $logger = LoggerFactory::getInstance('StashEdit');
     $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
     $key = self::getStashKey($title, self::getContentHash($content), $user);
     $editInfo = $cache->get($key);
     if (!is_object($editInfo)) {
         $start = microtime(true);
         // We ignore user aborts and keep parsing. Block on any prior parsing
         // so as to use its results and make use of the time spent parsing.
         // Skip this logic if there no master connection in case this method
         // is called on an HTTP GET request for some reason.
         $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
         $dbw = $lb->getAnyOpenConnection($lb->getWriterIndex());
         if ($dbw && $dbw->lock($key, __METHOD__, 30)) {
             $editInfo = $cache->get($key);
             $dbw->unlock($key, __METHOD__);
         }
         $timeMs = 1000 * max(0, microtime(true) - $start);
         $stats->timing('editstash.lock_wait_time', $timeMs);
     }
     if (!is_object($editInfo) || !$editInfo->output) {
         $stats->increment('editstash.cache_misses.no_stash');
         $logger->debug("Empty cache for key '{$key}' ('{$title}'); user '{$user->getName()}'.");
         return false;
     }
     $age = time() - wfTimestamp(TS_UNIX, $editInfo->output->getCacheTime());
     if ($age <= self::PRESUME_FRESH_TTL_SEC) {
         // Assume nothing changed in this time
         $stats->increment('editstash.cache_hits.presumed_fresh');
         $logger->debug("Timestamp-based cache hit for key '{$key}' (age: {$age} sec).");
     } elseif (isset($editInfo->edits) && $editInfo->edits === $user->getEditCount()) {
         // Logged-in user made no local upload/template edits in the meantime
         $stats->increment('editstash.cache_hits.presumed_fresh');
         $logger->debug("Edit count based cache hit for key '{$key}' (age: {$age} sec).");
     } elseif ($user->isAnon() && self::lastEditTime($user) < $editInfo->output->getCacheTime()) {
         // Logged-out user made no local upload/template edits in the meantime
         $stats->increment('editstash.cache_hits.presumed_fresh');
         $logger->debug("Edit check based cache hit for key '{$key}' (age: {$age} sec).");
     } else {
         // User may have changed included content
         $editInfo = false;
     }
     if (!$editInfo) {
         $stats->increment('editstash.cache_misses.proven_stale');
         $logger->info("Stale cache for key '{$key}'; old key with outside edits. (age: {$age} sec)");
     } elseif ($editInfo->output->getFlag('vary-revision')) {
         // This can be used for the initial parse, e.g. for filters or doEditContent(),
         // but a second parse will be triggered in doEditUpdates(). This is not optimal.
         $logger->info("Cache for key '{$key}' ('{$title}') has vary_revision.");
     } elseif ($editInfo->output->getFlag('vary-revision-id')) {
         // Similar to the above if we didn't guess the ID correctly.
         $logger->info("Cache for key '{$key}' ('{$title}') has vary_revision_id.");
     }
     return $editInfo;
 }
Example #2
0
 /**
  * Check that a prepared edit is in cache and still up-to-date
  *
  * This method blocks if the prepared edit is already being rendered,
  * waiting until rendering finishes before doing final validity checks.
  *
  * The cache is rejected if template or file changes are detected.
  * Note that foreign template or file transclusions are not checked.
  *
  * The result is a map (pstContent,output,timestamp) with fields
  * extracted directly from WikiPage::prepareContentForEdit().
  *
  * @param Title $title
  * @param Content $content
  * @param User $user User to get parser options from
  * @return stdClass|bool Returns false on cache miss
  */
 public static function checkCache(Title $title, Content $content, User $user)
 {
     if ($user->isBot()) {
         return false;
         // bots never stash - don't pollute stats
     }
     $cache = ObjectCache::getLocalClusterInstance();
     $logger = LoggerFactory::getInstance('StashEdit');
     $stats = RequestContext::getMain()->getStats();
     $key = self::getStashKey($title, $content, $user);
     $editInfo = $cache->get($key);
     if (!is_object($editInfo)) {
         $start = microtime(true);
         // We ignore user aborts and keep parsing. Block on any prior parsing
         // so as to use its results and make use of the time spent parsing.
         // Skip this logic if there no master connection in case this method
         // is called on an HTTP GET request for some reason.
         $lb = wfGetLB();
         $dbw = $lb->getAnyOpenConnection($lb->getWriterIndex());
         if ($dbw && $dbw->lock($key, __METHOD__, 30)) {
             $editInfo = $cache->get($key);
             $dbw->unlock($key, __METHOD__);
         }
         $timeMs = 1000 * max(0, microtime(true) - $start);
         $stats->timing('editstash.lock_wait_time', $timeMs);
     }
     if (!is_object($editInfo) || !$editInfo->output) {
         $stats->increment('editstash.cache_misses.no_stash');
         $logger->debug("No cache value for key '{$key}'.");
         return false;
     }
     $age = time() - wfTimestamp(TS_UNIX, $editInfo->output->getCacheTime());
     if ($age <= self::PRESUME_FRESH_TTL_SEC) {
         $stats->increment('editstash.cache_hits.presumed_fresh');
         $logger->debug("Timestamp-based cache hit for key '{$key}' (age: {$age} sec).");
         return $editInfo;
         // assume nothing changed
     } elseif (isset($editInfo->edits) && $editInfo->edits === $user->getEditCount()) {
         // Logged-in user made no local upload/template edits in the meantime
         $stats->increment('editstash.cache_hits.presumed_fresh');
         $logger->debug("Edit count based cache hit for key '{$key}' (age: {$age} sec).");
         return $editInfo;
     } elseif ($user->isAnon() && self::lastEditTime($user) < $editInfo->output->getCacheTime()) {
         // Logged-out user made no local upload/template edits in the meantime
         $stats->increment('editstash.cache_hits.presumed_fresh');
         $logger->debug("Edit check based cache hit for key '{$key}' (age: {$age} sec).");
         return $editInfo;
     }
     $dbr = wfGetDB(DB_SLAVE);
     $templates = [];
     // conditions to find changes/creations
     $templateUses = 0;
     // expected existing templates
     foreach ($editInfo->output->getTemplateIds() as $ns => $stuff) {
         foreach ($stuff as $dbkey => $revId) {
             $templates[(string) $ns][$dbkey] = (int) $revId;
             ++$templateUses;
         }
     }
     // Check that no templates used in the output changed...
     if (count($templates)) {
         $res = $dbr->select('page', ['ns' => 'page_namespace', 'dbk' => 'page_title', 'page_latest'], $dbr->makeWhereFrom2d($templates, 'page_namespace', 'page_title'), __METHOD__);
         $changed = false;
         foreach ($res as $row) {
             $changed = $changed || $row->page_latest != $templates[$row->ns][$row->dbk];
         }
         if ($changed || $res->numRows() != $templateUses) {
             $stats->increment('editstash.cache_misses.proven_stale');
             $logger->info("Stale cache for key '{$key}'; template changed. (age: {$age} sec)");
             return false;
         }
     }
     $files = [];
     // conditions to find changes/creations
     foreach ($editInfo->output->getFileSearchOptions() as $name => $options) {
         $files[$name] = (string) $options['sha1'];
     }
     // Check that no files used in the output changed...
     if (count($files)) {
         $res = $dbr->select('image', ['name' => 'img_name', 'img_sha1'], ['img_name' => array_keys($files)], __METHOD__);
         $changed = false;
         foreach ($res as $row) {
             $changed = $changed || $row->img_sha1 != $files[$row->name];
         }
         if ($changed || $res->numRows() != count($files)) {
             $stats->increment('editstash.cache_misses.proven_stale');
             $logger->info("Stale cache for key '{$key}'; file changed. (age: {$age} sec)");
             return false;
         }
     }
     $stats->increment('editstash.cache_hits.proven_fresh');
     $logger->debug("Verified cache hit for key '{$key}' (age: {$age} sec).");
     return $editInfo;
 }