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:
parent
e4cb5d4be7
commit
1353a8ba29
8 changed files with 629 additions and 363 deletions
|
|
@ -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
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
64
includes/PoolCounter.php
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -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}}',
|
||||
|
|
|
|||
Loading…
Reference in a new issue