From dd58309f1a78b907f1c043a3002d0aa03ef3a04d Mon Sep 17 00:00:00 2001 From: Alexandre Emsenhuber Date: Thu, 17 Nov 2011 20:21:54 +0000 Subject: [PATCH] * 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 --- docs/memcached.txt | 2 +- includes/Article.php | 262 +++-------------------------- includes/AutoLoader.php | 1 + includes/WikiPage.php | 226 ++++++++++++++++++++++++- includes/diff/DifferenceEngine.php | 11 +- 5 files changed, 254 insertions(+), 248 deletions(-) diff --git a/docs/memcached.txt b/docs/memcached.txt index f4d93c38f5..f5384f9d75 100644 --- a/docs/memcached.txt +++ b/docs/memcached.txt @@ -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 diff --git a/includes/Article.php b/includes/Article.php index 7c359fbd0e..0b5f7d0834 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -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( '
' . $errortext . '
' ); + } # 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( "\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 ) . "" ); } - /** - * 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( "\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; - $user = is_null( $user ) ? $wgUser : $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; - } + global $wgUser; - if ( $this->isCurrent() ) { - $this->mPage->doCascadeProtectionUpdates( $this->mParserOutput ); - } + $user = is_null( $user ) ? $wgUser : $user; + $parserOptions = $this->mPage->makeParserOptions( $user ); - 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( '
' . $errortext . '
' ); - - return false; - } -} diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 6594831157..fc4467d1d3 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -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', diff --git a/includes/WikiPage.php b/includes/WikiPage.php index 3f0183e5e5..1de6c1783a 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -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; } } diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index 08bc3924d4..a57d4b08ec 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -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 ); } } } -- 2.20.1