Implemented the PoolCounter feature and did some general refactoring in the areas that it touched.

* Renamed Article::outputFromWikitext() to Article::getOutputFromWikitext()
* Factored out cascade protection updates
* Removed recently-added Article::tryParserCache(): misnamed, can be done in one line of code in the caller. Deprecated OutputPage::tryParserCache().
* Made some functions public instead of protected when they could be useful from hooks.
* In ParserCache, removed PHP 4-style ampersands

In Article::view():
* Factored out robot policy logic, "redirected from" header, patrol footer, diff page, revdelete header, CSS/JS formatting, footer, namespace header, missing article error
* Removed some variables, renamed some others, fixed incorrect use of empty()
* Used the refactored footer section to do a couple of early returns and unindent a massive if(!$outputDone) block
* Removed fantasy interpretation of $this->getContent()===false in comment
* Don't try the parser cache when ArticleViewHeader specified $outputDone=true
* Move timing hack to getOutputFromWikitext()
* Stop using $wgOut->parserOptions() with save/restore nonsense every time you want to change something in it. This is meant to be OOP.
* Don't overwrite the article text with an error message and then pretend to write it to the cache, that's confusing
This commit is contained in:
Tim Starling 2009-07-08 08:12:35 +00:00
parent e4cb5d4be7
commit 1353a8ba29
8 changed files with 629 additions and 363 deletions

View file

@ -110,6 +110,9 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
set $wgCacheDirectory to get a faster CDB-based implementation.
* Expanded the number of variables which can be set in the extension messages
files.
* Added a feature to allow per-article process pool size control for the parsing
task, to limit resource usage when the cache for a heavily-viewed article is
invalidated. Requires an external daemon.
=== Bug fixes in 1.16 ===

File diff suppressed because it is too large Load diff

View file

@ -158,6 +158,8 @@ $wgAutoloadLocalClasses = array(
'Pager' => 'includes/Pager.php',
'PasswordError' => 'includes/User.php',
'PatrolLog' => 'includes/PatrolLog.php',
'PoolCounter' => 'includes/PoolCounter.php',
'PoolCounter_Stub' => 'includes/PoolCounter.php',
'PostgresSearchResult' => 'includes/SearchPostgres.php',
'PostgresSearchResultSet' => 'includes/SearchPostgres.php',
'Preferences' => 'includes/Preferences.php',

View file

@ -3884,3 +3884,20 @@ $wgInvalidUsernameCharacters = '@';
* modify the user rights of those users via Special:UserRights
*/
$wgUserrightsInterwikiDelimiter = '@';
/**
* Configuration for processing pool control, for use in high-traffic wikis.
* An implementation is provided in the PoolCounter extension.
*
* This configuration array maps pool types to an associative array. The only
* defined key in the associative array is "class", which gives the class name.
* The remaining elements are passed through to the class as constructor
* parameters. Example:
*
* $wgPoolCounterConf = array( 'Article::view' => array(
* 'class' => 'PoolCounter_Client',
* ... any extension-specific options...
* );
*/
$wgPoolCounterConf = null;

View file

@ -715,12 +715,13 @@ class OutputPage {
* @param Article $article
* @param User $user
*
* Now a wrapper around Article::tryParserCache()
* @deprecated
*
* @return bool True if successful, else false.
*/
public function tryParserCache( &$article ) {
$parserOutput = $article->tryParserCache( $this->parserOptions() );
wfDeprecated( __METHOD__ );
$parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
if ($parserOutput !== false) {
$this->addParserOutput( $parserOutput );

64
includes/PoolCounter.php Normal file
View file

@ -0,0 +1,64 @@
<?php
abstract class PoolCounter {
public function factory( $type, $key ) {
global $wgPoolCounterConf;
if ( !isset( $wgPoolCounterConf[$type] ) ) {
return new PoolCounter_Stub;
}
$conf = $wgPoolCounterConf[$type];
$class = $conf['class'];
return new $class( $conf, $type, $key );
}
abstract public function acquire();
abstract public function release();
abstract public function wait();
public function executeProtected( $mainCallback, $dirtyCallback = false ) {
$status = $this->acquire();
if ( !$status->isOK() ) {
return $status;
}
if ( !empty( $status->value['overload'] ) ) {
# Overloaded. Try a dirty cache entry.
if ( $dirtyCallback ) {
if ( call_user_func( $dirtyCallback ) ) {
$this->release();
return Status::newGood();
}
}
# Wait for a thread
$status = $this->wait();
if ( !$status->isOK() ) {
$this->release();
return $status;
}
}
# Call the main callback
call_user_func( $mainCallback );
return $this->release();
}
}
class PoolCounter_Stub extends PoolCounter {
public function acquire() {
return Status::newGood();
}
public function release() {
return Status::newGood();
}
public function wait() {
return Status::newGood();
}
public function executeProtected( $mainCallback, $dirtyCallback = false ) {
call_user_func( $mainCallback );
return Status::newGood();
}
}

View file

@ -7,7 +7,7 @@ class ParserCache {
/**
* Get an instance of this object
*/
public static function &singleton() {
public static function singleton() {
static $instance;
if ( !isset( $instance ) ) {
global $parserMemc;
@ -22,11 +22,11 @@ class ParserCache {
*
* @param object $memCached
*/
function __construct( &$memCached ) {
$this->mMemc =& $memCached;
function __construct( $memCached ) {
$this->mMemc = $memCached;
}
function getKey( &$article, $popts ) {
function getKey( $article, $popts ) {
global $wgRequest;
if( $popts instanceof User ) // It used to be getKey( &$article, &$user )
@ -47,52 +47,55 @@ class ParserCache {
return $key;
}
function getETag( &$article, $popts ) {
function getETag( $article, $popts ) {
return 'W/"' . $this->getKey($article, $popts) . "--" . $article->mTouched. '"';
}
function get( &$article, $popts ) {
global $wgCacheEpoch;
$fname = 'ParserCache::get';
wfProfileIn( $fname );
function getDirty( $article, $popts ) {
$key = $this->getKey( $article, $popts );
wfDebug( "Trying parser cache $key\n" );
$value = $this->mMemc->get( $key );
if ( is_object( $value ) ) {
wfDebug( "Found.\n" );
# Delete if article has changed since the cache was made
$canCache = $article->checkTouched();
$cacheTime = $value->getCacheTime();
$touched = $article->mTouched;
if ( !$canCache || $value->expired( $touched ) ) {
if ( !$canCache ) {
wfIncrStats( "pcache_miss_invalid" );
wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
} else {
wfIncrStats( "pcache_miss_expired" );
wfDebug( "Key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
}
$this->mMemc->delete( $key );
$value = false;
} else {
if ( isset( $value->mTimestamp ) ) {
$article->mTimestamp = $value->mTimestamp;
}
wfIncrStats( "pcache_hit" );
}
} else {
return is_object( $value ) ? $value : false;
}
function get( $article, $popts ) {
global $wgCacheEpoch;
wfProfileIn( __METHOD__ );
$value = $this->getDirty( $article, $popts );
if ( !$value ) {
wfDebug( "Parser cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
$value = false;
wfProfileOut( __METHOD__ );
return false;
}
wfProfileOut( $fname );
wfDebug( "Found.\n" );
# Invalid if article has changed since the cache was made
$canCache = $article->checkTouched();
$cacheTime = $value->getCacheTime();
$touched = $article->mTouched;
if ( !$canCache || $value->expired( $touched ) ) {
if ( !$canCache ) {
wfIncrStats( "pcache_miss_invalid" );
wfDebug( "Invalid cached redirect, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
} else {
wfIncrStats( "pcache_miss_expired" );
wfDebug( "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;
}
function save( $parserOutput, &$article, $popts ){
function save( $parserOutput, $article, $popts ){
global $wgParserCacheExpireTime;
$key = $this->getKey( $article, $popts );

View file

@ -846,6 +846,11 @@ XHTML id names.
'jumpto' => 'Jump to:',
'jumptonavigation' => 'navigation',
'jumptosearch' => 'search',
'view-pool-error' => 'Sorry, our servers are overloaded at the moment.
Too many people are trying to view this article.
Please try again in a minute or two.
$1',
# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage) and the disambiguation template definition (see disambiguations).
'aboutsite' => 'About {{SITENAME}}',