* Added WikiPage::getParserOutput() and changed Article::getParserOutput() to use it

* WikiPage::getParserOutput() requires a ParserOptions object (and optionally the revision ID) instead of an User object, removes an hidden dependency on $wgLang. For this reason, WikiPage::isParserCacheUsed() now also uses a ParserOptions object instead of an User object (doesn't change anything in the code except the variable name and it's not called in extensions)
* Moved PoolWorkArticleView to WikiPage.php and added an entry in the AutoLoader and moved output-related stuff directly in Article::view() so that in can be shared with WikiPage::getParserOutput() (removes code duplication, etc.)
* Added the revision ID to the PoolCounter key so that it knows which revision is being parsed and doesn't wait for another parse operation with same options but different revisions
* Removed Article::doViewParse(), Article::tryDirtyCache() and Article::getOutputFromWikitext() since they are now integrated in PoolWorkArticleView and Article::view() and there are no callers in extensions. This also fixes a bug since Article::doViewParse() will get another ParserOptions instance with special options set in Article::view() not be repercuted.
* Updated DifferenceEngine to use the new system
* Updated docs/memcached.txt to correct method names
This commit is contained in:
Alexandre Emsenhuber 2011-11-17 20:21:54 +00:00
parent fcb28f12ea
commit dd58309f1a
5 changed files with 254 additions and 248 deletions

View file

@ -159,7 +159,7 @@ Parser Cache:
$hash: hash of user options applied to the page, see ParserOptions::optionsHash()
ex: wikidb:pcache:idhash:1-0!1!0!!en!2
stores: ParserOutput object
modified by: Article::editUpdates() or Article::getOutputFromWikitext()
modified by: WikiPage::doEditUpdates() or PoolWorkArticleView::doWork()
expiry: $wgParserCacheExpireTime or less if it contains short lived functions
key: $wgDBname:pcache:idoptions:$pageid

View file

@ -453,7 +453,7 @@ class Article extends Page {
}
# Should the parser cache be used?
$useParserCache = $this->mPage->isParserCacheUsed( $wgUser, $oldid );
$useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $wgUser->getStubThreshold() ) {
wfIncrStats( 'pcache_miss_stub' );
@ -550,16 +550,34 @@ class Article extends Page {
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
$key = $parserCache->getKey( $this, $parserOptions );
$poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions );
$poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
$this->getRevIdFetched(), $useParserCache, $this->getContent() );
if ( !$poolArticleView->execute() ) {
$error = $poolArticleView->getError();
if ( $error ) {
$wgOut->clearHTML(); // for release() errors
$wgOut->enableClientCache( false );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$errortext = $error->getWikiText( false, 'view-pool-error' );
$wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
}
# Connection or timeout error
wfProfileOut( __METHOD__ );
return;
} else {
$outputDone = true;
}
$this->mParserOutput = $poolArticleView->getParserOutput();
$wgOut->addParserOutput( $this->mParserOutput );
# Don't cache a dirty ParserOutput object
if ( $poolArticleView->getIsDirty() ) {
$wgOut->setSquidMaxage( 0 );
$wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
}
$outputDone = true;
break;
# Should be unreachable, but just in case...
default:
@ -1149,67 +1167,6 @@ class Article extends Page {
$prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>" );
}
/**
* Execute the uncached parse for action=view
* @return bool
*/
public function doViewParse() {
global $wgOut;
$oldid = $this->getOldID();
$parserOptions = $this->getParserOptions();
# Render printable version, use printable version cache
$parserOptions->setIsPrintable( $wgOut->isPrintable() );
# Don't show section-edit links on old revisions... this way lies madness.
if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
$parserOptions->setEditSection( false );
}
$useParserCache = $this->useParserCache( $oldid );
$this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
return true;
}
/**
* Try to fetch an expired entry from the parser cache. If it is present,
* output it and return true. If it is not present, output nothing and
* return false. This is used as a callback function for
* PoolCounter::executeProtected().
*
* @return boolean
*/
public function tryDirtyCache() {
global $wgOut;
$parserCache = ParserCache::singleton();
$options = $this->getParserOptions();
if ( $wgOut->isPrintable() ) {
$options->setIsPrintable( true );
$options->setEditSection( false );
}
$output = $parserCache->getDirty( $this, $options );
if ( $output ) {
wfDebug( __METHOD__ . ": sending dirty output\n" );
wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
$wgOut->setSquidMaxage( 0 );
$this->mParserOutput = $output;
$wgOut->addParserOutput( $output );
$wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
return true;
} else {
wfDebugLog( 'dirty', "dirty missing\n" );
wfDebug( __METHOD__ . ": no dirty cache\n" );
return false;
}
}
/**
* View redirect
*
@ -1641,25 +1598,6 @@ class Article extends Page {
/**#@-*/
/**
* Add the primary page-view wikitext to the output buffer
* Saves the text into the parser cache if possible.
* Updates templatelinks if it is out of date.
*
* @param $text String
* @param $cache Boolean
* @param $parserOptions mixed ParserOptions object, or boolean false
*/
public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
global $wgOut;
$this->mParserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
$this->doCascadeProtectionUpdates( $this->mParserOutput );
$wgOut->addParserOutput( $this->mParserOutput );
}
/**
* Lightweight method to get the parser output for a page, checking the parser cache
* and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
@ -1672,93 +1610,12 @@ class Article extends Page {
* @return ParserOutput or false if the given revsion ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
global $wgEnableParserCache, $wgUser;
global $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
$parserOptions = $this->mPage->makeParserOptions( $user );
wfProfileIn( __METHOD__ );
// Should the parser cache be used?
$useParserCache = $wgEnableParserCache &&
$user->getStubThreshold() == 0 &&
$this->mPage->exists() &&
$oldid === null;
wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $user->getStubThreshold() ) {
wfIncrStats( 'pcache_miss_stub' );
}
if ( $useParserCache ) {
$options = $this->mPage->makeParserOptions( $user );
$parserOutput = ParserCache::singleton()->get( $this, $options );
if ( $parserOutput !== false ) {
wfProfileOut( __METHOD__ );
return $parserOutput;
}
}
// Cache miss; parse and output it.
if ( $oldid === null ) {
$text = $this->mPage->getRawText();
} else {
$rev = Revision::newFromTitle( $this->getTitle(), $oldid );
if ( $rev === null ) {
wfProfileOut( __METHOD__ );
return false;
}
$text = $rev->getText();
}
$output = $this->getOutputFromWikitext( $text, $useParserCache );
wfProfileOut( __METHOD__ );
return $output;
}
/**
* This does all the heavy lifting for outputWikitext, except it returns the parser
* output instead of sending it straight to $wgOut. Makes things nice and simple for,
* say, embedding thread pages within a discussion system (LiquidThreads)
*
* @param $text string
* @param $cache boolean
* @param $parserOptions parsing options, defaults to false
* @return ParserOutput
*/
public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
global $wgParser, $wgEnableParserCache, $wgUseFileCache;
if ( !$parserOptions ) {
$parserOptions = $this->getParserOptions();
}
$time = - wfTime();
$this->mParserOutput = $wgParser->parse( $text, $this->getTitle(),
$parserOptions, true, true, $this->getRevIdFetched() );
$time += wfTime();
# Timing hack
if ( $time > 3 ) {
wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
$this->getTitle()->getPrefixedDBkey() ) );
}
if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
$parserCache = ParserCache::singleton();
$parserCache->save( $this->mParserOutput, $this, $parserOptions );
}
// Make sure file cache is not used on uncacheable content.
// Output that has magic words in it can still use the parser cache
// (if enabled), though it will generally expire sooner.
if ( !$this->mParserOutput->isCacheable() || $this->mParserOutput->containsOldMagic() ) {
$wgUseFileCache = false;
}
if ( $this->isCurrent() ) {
$this->mPage->doCascadeProtectionUpdates( $this->mParserOutput );
}
return $this->mParserOutput;
return $this->mPage->getParserOutput( $parserOptions, $oldid );
}
/**
@ -2063,68 +1920,3 @@ class Article extends Page {
}
// ******
}
class PoolWorkArticleView extends PoolCounterWork {
/**
* @var Article
*/
private $mArticle;
function __construct( $article, $key, $useParserCache, $parserOptions ) {
parent::__construct( 'ArticleView', $key );
$this->mArticle = $article;
$this->cacheable = $useParserCache;
$this->parserOptions = $parserOptions;
}
/**
* @return bool
*/
function doWork() {
return $this->mArticle->doViewParse();
}
/**
* @return bool
*/
function getCachedWork() {
global $wgOut;
$parserCache = ParserCache::singleton();
$this->mArticle->mParserOutput = $parserCache->get( $this->mArticle, $this->parserOptions );
if ( $this->mArticle->mParserOutput !== false ) {
wfDebug( __METHOD__ . ": showing contents parsed by someone else\n" );
$wgOut->addParserOutput( $this->mArticle->mParserOutput );
# Ensure that UI elements requiring revision ID have
# the correct version information.
$wgOut->setRevisionId( $this->mArticle->getLatest() );
return true;
}
return false;
}
/**
* @return bool
*/
function fallback() {
return $this->mArticle->tryDirtyCache();
}
/**
* @param $status Status
*/
function error( $status ) {
global $wgOut;
$wgOut->clearHTML(); // for release() errors
$wgOut->enableClientCache( false );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$errortext = $status->getWikiText( false, 'view-pool-error' );
$wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
return false;
}
}

View file

@ -168,6 +168,7 @@ $wgAutoloadLocalClasses = array(
'PoolCounter' => 'includes/PoolCounter.php',
'PoolCounter_Stub' => 'includes/PoolCounter.php',
'PoolCounterWork' => 'includes/PoolCounter.php',
'PoolWorkArticleView' => 'includes/WikiPage.php',
'Preferences' => 'includes/Preferences.php',
'PreferencesForm' => 'includes/Preferences.php',
'PrefixSearch' => 'includes/PrefixSearch.php',

View file

@ -708,20 +708,61 @@ class WikiPage extends Page {
/**
* Should the parser cache be used?
*
* @param $user User The relevant user
* @param $parserOptions ParserOptions to check
* @param $oldid int
* @return boolean
*/
public function isParserCacheUsed( User $user, $oldid ) {
public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
global $wgEnableParserCache;
return $wgEnableParserCache
&& $user->getStubThreshold() == 0
&& $parserOptions->getStubThreshold() == 0
&& $this->exists()
&& ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
&& $this->mTitle->isWikitextPage();
}
/**
* Get a ParserOutput for the given ParserOptions and revision ID.
* The the parser cache will be used if possible.
*
* @since 1.19
* @param $parserOptions ParserOptions to use for the parse operation
* @param $oldid Revision ID to get the text from, passing null or 0 will
* get the current revision (default value)
* @return ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
global $wgParser;
wfProfileIn( __METHOD__ );
$useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $parserOptions->getStubThreshold() ) {
wfIncrStats( 'pcache_miss_stub' );
}
if ( $useParserCache ) {
$parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
if ( $parserOutput !== false ) {
wfProfileOut( __METHOD__ );
return $parserOutput;
}
}
if ( $oldid === null || $oldid === 0 ) {
$oldid = $this->getLatest();
}
$pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
$pool->execute();
wfProfileOut( __METHOD__ );
return $pool->getParserOutput();
}
/**
* Perform the actions of a page purging
*/
@ -2671,6 +2712,183 @@ class WikiPage extends Page {
*/
public function useParserCache( $oldid ) {
global $wgUser;
return $this->isParserCacheUsed( $wgUser, $oldid );
return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
}
}
class PoolWorkArticleView extends PoolCounterWork {
/**
* @var Page
*/
private $page;
/**
* @var string
*/
private $cacheKey;
/**
* @var integer
*/
private $revid;
/**
* @var ParserOptions
*/
private $parserOptions;
/**
* @var string|null
*/
private $text;
/**
* @var ParserOutput|false
*/
private $parserOutput = false;
/**
* @var bool
*/
private $isDirty = false;
/**
* @var Status|false
*/
private $error = false;
/**
* Constructor
*
* @param $page Page
* @param $revid Integer: ID of the revision being parsed
* @param $useParserCache Boolean: whether to use the parser cache
* @param $parserOptions parserOptions to use for the parse operation
* @param $text String: text to parse or null to load it
*/
function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
$this->page = $page;
$this->revid = $revid;
$this->cacheable = $useParserCache;
$this->parserOptions = $parserOptions;
$this->text = $text;
$this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
}
/**
* Get the ParserOutput from this object, or false in case of failure
*
* @return ParserOutput
*/
public function getParserOutput() {
return $this->parserOutput;
}
/**
* Get whether the ParserOutput is a dirty one (i.e. expired)
*
* @return bool
*/
public function getIsDirty() {
return $this->isDirty();
}
/**
* Get a Status object in case of error or false otherwise
*
* @return Status|false
*/
public function getError() {
return $this->error;
}
/**
* @return bool
*/
function doWork() {
global $wgParser, $wgUseFileCache;
$isCurrent = $this->revid === $this->page->getLatest();
if ( $this->text !== null ) {
$text = $this->text;
} elseif ( $isCurrent ) {
$text = $this->page->getRawText();
} else {
$rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
if ( $rev === null ) {
return false;
}
$text = $rev->getText();
}
$time = - wfTime();
$this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
$this->parserOptions, true, true, $this->revid );
$time += wfTime();
# Timing hack
if ( $time > 3 ) {
wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
$this->page->getTitle()->getPrefixedDBkey() ) );
}
if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
ParserCache::singleton()->save( $this->parserOutput, $this->page, $this->parserOptions );
}
// Make sure file cache is not used on uncacheable content.
// Output that has magic words in it can still use the parser cache
// (if enabled), though it will generally expire sooner.
if ( !$this->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
$wgUseFileCache = false;
}
if ( $isCurrent ) {
$this->page->doCascadeProtectionUpdates( $this->parserOutput );
}
}
/**
* @return bool
*/
function getCachedWork() {
$this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
if ( $this->parserOutput === false ) {
wfDebug( __METHOD__ . ": parser cache miss\n" );
return false;
} else {
wfDebug( __METHOD__ . ": parser cache hit\n" );
return true;
}
}
/**
* @return bool
*/
function fallback() {
$this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
if ( $this->parserOutput === false ) {
wfDebugLog( 'dirty', "dirty missing\n" );
wfDebug( __METHOD__ . ": no dirty cache\n" );
return false;
} else {
wfDebug( __METHOD__ . ": sending dirty output\n" );
wfDebugLog( 'dirty', "dirty output {$this->cacheKey}\n" );
$this->isDirty = true;
return true;
}
}
/**
* @param $status Status
*/
function error( $status ) {
$this->error = $status;
return false;
}
}

View file

@ -516,9 +516,8 @@ class DifferenceEngine extends ContextSource {
} elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
// Handled by extension
} else {
# Use the current version parser cache if applicable
// Normal page
$wikiPage = WikiPage::factory( $this->mNewPage );
$useParserCache = $wikiPage->isParserCacheUsed( $this->getUser(), $this->mNewid );
$parserOptions = ParserOptions::newFromContext( $this->getContext() );
$parserOptions->enableLimitReport();
@ -528,15 +527,11 @@ class DifferenceEngine extends ContextSource {
$parserOptions->setEditSection( false );
}
$parserOutput = false;
if ( $useParserCache ) {
$parserOutput = ParserCache::singleton()->get( $wikiPage, $parserOptions );
}
$parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
# WikiPage::getParserOutput() should not return false, but just in case
if( $parserOutput ) {
$out->addParserOutput( $parserOutput );
} else {
$out->addWikiTextTidy( $this->mNewtext );
}
}
}