wiki.techinc.nl/includes/parser/ParserCache.php
Platonides 34d35fb6f9 Use only the page relevant pieces in the parser cache key. Eg. two users with different math options will now
use the same parsercache entry for articles without <math> tags.
The cache key format is kept as a fallback so the old cached entries can be reused.

Should boost parsercache hits, but it also makes easier to pollute the parsercache by tag hooks that behave 
badly, directly using $wgUser or $wgLang.

Extensions hooking PageRenderingHash now see !edit=0 and the !printable=1 bits.

Fixes bug 24714 - Usage of {{#dateformat: }} in wikis without $wgUseDynamicDates can lead to unexpected results

Builds upon r70498, r70498, r70501, r70517, r70651, r70653, r70765, r70780.
2010-08-09 21:53:21 +00:00

178 lines
5 KiB
PHP

<?php
/**
* @ingroup Cache Parser
* @todo document
*/
class ParserCache {
private $mMemc;
/**
* Get an instance of this object
*/
public static function singleton() {
static $instance;
if ( !isset( $instance ) ) {
global $parserMemc;
$instance = new ParserCache( $parserMemc );
}
return $instance;
}
/**
* Setup a cache pathway with a given back-end storage mechanism.
* May be a memcached client or a BagOStuff derivative.
*
* @param $memCached Object
*/
function __construct( $memCached ) {
if ( !$memCached ) {
global $parserMemc;
$parserMemc = $memCached = wfGetParserCacheStorage();
}
$this->mMemc = $memCached;
}
protected function getParserOutputKey( $article, $hash ) {
global $wgRequest;
// idhash seem to mean 'page id' + 'rendering hash' (r3710)
$pageid = $article->getID();
$renderkey = (int)($wgRequest->getVal('action') == 'render');
$key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
return $key;
}
protected function getOptionsKey( $article ) {
$pageid = $article->getID();
return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
}
function getETag( $article, $popts ) {
return 'W/"' . $this->getParserOutputKey( $article,
$popts->optionsHash( ParserOptions::legacyOptions() ) ) .
"--" . $article->mTouched . '"';
}
/**
* Retrieve the ParserOutput from ParserCache, even if it's outdated.
*/
public function getDirty( $article, $popts ) {
$value = $this->mMemc->get( $article, $popts, true );
return is_object( $value ) ? $value : false;
}
/**
* Used to provide a unique id for the PoolCounter.
* It would be preferable to have this code in get()
* instead of having Article looking in our internals.
*
* Precondition: $article->checkTouched() has been called.
*/
public function getKey( $article, $popts, $useOutdated = true ) {
global $wgCacheEpoch;
// Determine the options which affect this article
$optionsKey = $this->mMemc->get( $this->getOptionsKey( $article ) );
if ( $optionsKey !== false ) {
if ( !$useOutdated && $optionsKey->expired( $article->mTouched ) ) {
wfIncrStats( "pcache_miss_expired" );
$cacheTime = $optionsKey->getCacheTime();
wfDebug( "Parser options key expired, touched {$article->mTouched}, epoch $wgCacheEpoch, cached $cacheTime\n" );
return false;
}
$usedOptions = $optionsKey->mUsedOptions;
wfDebug( "Parser cache options found.\n" );
} else {
# TODO: Fail here $wgParserCacheExpireTime after deployment unless $useOutdated
$usedOptions = ParserOptions::legacyOptions();
}
return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions ) );
}
/**
* Retrieve the ParserOutput from ParserCache.
* false if not found or outdated.
*/
public function get( $article, $popts, $useOutdated = false ) {
global $wgCacheEpoch;
wfProfileIn( __METHOD__ );
$canCache = $article->checkTouched();
if ( !$canCache ) {
// It's a redirect now
wfProfileOut( __METHOD__ );
return false;
}
// Having called checkTouched() ensures this will be loaded
$touched = $article->mTouched;
$parserOutputKey = $this->getKey( $article, $popts, $useOutdated );
if ( $parserOutputKey === false ) {
wfProfileOut( __METHOD__ );
return false;
}
$value = $this->mMemc->get( $parserOutputKey );
if ( !$value ) {
wfDebug( "Parser cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
wfProfileOut( __METHOD__ );
return false;
}
wfDebug( "Found.\n" );
if ( !$useOutdated && $value->expired( $touched ) ) {
wfIncrStats( "pcache_miss_expired" );
wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
$value = false;
} else {
if ( isset( $value->mTimestamp ) ) {
$article->mTimestamp = $value->mTimestamp;
}
wfIncrStats( "pcache_hit" );
}
wfProfileOut( __METHOD__ );
return $value;
}
public function save( $parserOutput, $article, $popts ) {
$expire = $parserOutput->getCacheExpiry();
if( $expire > 0 ) {
$now = wfTimestampNow();
$optionsKey = new CacheTime;
$optionsKey->mUsedOptions = $popts->usedOptions();
$optionsKey->updateCacheExpiry( $expire );
$optionsKey->setCacheTime( $now );
$parserOutput->setCacheTime( $now );
$optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
$parserOutputKey = $this->getParserOutputKey( $article, $popts->optionsHash( $optionsKey->mUsedOptions ) );
// Save the timestamp so that we don't have to load the revision row on view
$parserOutput->mTimestamp = $article->getTimestamp();
$parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $now -->\n";
wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $now\n" );
// Save the parser output
$this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
// ...and its pointer
$this->mMemc->set( $this->getOptionsKey( $article ), $optionsKey, $expire );
} else {
wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
}
}
}