summaryrefslogtreecommitdiff
path: root/includes/cache/MessageCache.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/cache/MessageCache.php')
-rw-r--r--includes/cache/MessageCache.php243
1 files changed, 81 insertions, 162 deletions
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index b854a2ec..c406b5c3 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -60,26 +60,6 @@ class MessageCache {
protected $mLoadedLanguages = array();
/**
- * Used for automatic detection of most used messages.
- */
- protected $mRequestedMessages = array();
-
- /**
- * How long the message request counts are stored. Longer period gives
- * better sample, but also takes longer to adapt changes. The counts
- * are aggregrated per day, regardless of the value of this variable.
- */
- protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
-
- /**
- * Filter the tail of less used messages that are requested more seldom
- * than this factor times the number of request of most requested message.
- * These messages are not loaded in the default set, but are still cached
- * individually on demand with the normal cache expiry time.
- */
- protected static $mAdaptiveInclusionThreshold = 0.05;
-
- /**
* Singleton instance
*
* @var MessageCache
@@ -142,7 +122,7 @@ class MessageCache {
* Actual format of the file depends on the $wgLocalMessageCacheSerialized
* setting.
*
- * @param $hash String: the hash of contents, to check validity.
+ * @param string $hash the hash of contents, to check validity.
* @param $code Mixed: Optional language code, see documenation of load().
* @return bool on failure.
*/
@@ -277,12 +257,15 @@ class MessageCache {
* or false if populating empty cache fails. Also returns true if MessageCache
* is disabled.
*
- * @param $code String: language to which load messages
+ * @param bool|String $code String: language to which load messages
+ * @throws MWException
* @return bool
*/
function load( $code = false ) {
global $wgUseLocalMessageCache;
+ $exception = null; // deferred error
+
if( !is_string( $code ) ) {
# This isn't really nice, so at least make a note about it and try to
# fall back
@@ -344,35 +327,52 @@ class MessageCache {
$where[] = 'cache is empty';
$where[] = 'loading from database';
- $this->lock( $cacheKey );
-
+ if ( $this->lock( $cacheKey ) ) {
+ $that = $this;
+ $osc = new ScopedCallback( function() use ( $that, $cacheKey ) {
+ $that->unlock( $cacheKey );
+ } );
+ }
# Limit the concurrency of loadFromDB to a single process
# This prevents the site from going down when the cache expires
$statusKey = wfMemcKey( 'messages', $code, 'status' );
$success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
- if ( $success ) {
+ if ( $success ) { // acquired lock
+ $cache = $this->mMemc;
+ $isc = new ScopedCallback( function() use ( $cache, $statusKey ) {
+ $cache->delete( $statusKey );
+ } );
$cache = $this->loadFromDB( $code );
$success = $this->setCache( $cache, $code );
- }
- if ( $success ) {
- $success = $this->saveToCaches( $cache, true, $code );
- if ( $success ) {
- $this->mMemc->delete( $statusKey );
+ if ( $success ) { // messages loaded
+ $success = $this->saveToCaches( $cache, true, $code );
+ $isc = null; // unlock
+ if ( !$success ) {
+ $this->mMemc->set( $statusKey, 'error', 60 * 5 );
+ wfDebug( __METHOD__ . ": set() error: restart memcached server!\n" );
+ $exception = new MWException( "Could not save cache for '$code'." );
+ }
} else {
- $this->mMemc->set( $statusKey, 'error', 60 * 5 );
- wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
+ $isc = null; // unlock
+ $exception = new MWException( "Could not load cache from DB for '$code'." );
}
+ } else {
+ $exception = new MWException( "Could not acquire '$statusKey' lock." );
}
- $this->unlock($cacheKey);
+ $osc = null; // unlock
}
if ( !$success ) {
- # Bad luck... this should not happen
- $where[] = 'loading FAILED - cache is disabled';
- $info = implode( ', ', $where );
- wfDebug( __METHOD__ . ": Loading $code... $info\n" );
$this->mDisable = true;
$this->mCache = false;
+ // This used to go on, but that led to lots of nasty side
+ // effects like gadgets and sidebar getting cached with their
+ // default content
+ if ( $exception instanceof Exception ) {
+ throw $exception;
+ } else {
+ throw new MWException( "MessageCache failed to load messages" );
+ }
} else {
# All good, just record the success
$info = implode( ', ', $where );
@@ -388,7 +388,7 @@ class MessageCache {
* $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
* on-demand from the database later.
*
- * @param $code String: language code.
+ * @param string $code language code.
* @return Array: loaded messages for storing in caches.
*/
function loadFromDB( $code ) {
@@ -404,19 +404,20 @@ class MessageCache {
);
$mostused = array();
- if ( $wgAdaptiveMessageCache ) {
- $mostused = $this->getMostUsedMessages();
- if ( $code !== $wgLanguageCode ) {
- foreach ( $mostused as $key => $value ) {
- $mostused[$key] = "$value/$code";
- }
+ if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
+ if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
+ $this->load( $wgLanguageCode );
+ }
+ $mostused = array_keys( $this->mCache[$wgLanguageCode] );
+ foreach ( $mostused as $key => $value ) {
+ $mostused[$key] = "$value/$code";
}
}
if ( count( $mostused ) ) {
$conds['page_title'] = $mostused;
} elseif ( $code !== $wgLanguageCode ) {
- $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
+ $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $code );
} else {
# Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
# other than language code.
@@ -459,12 +460,6 @@ class MessageCache {
$cache[$row->page_title] = $entry;
}
- foreach ( $mostused as $key ) {
- if ( !isset( $cache[$key] ) ) {
- $cache[$key] = '!NONEXISTENT';
- }
- }
-
$cache['VERSION'] = MSG_CACHE_VERSION;
wfProfileOut( __METHOD__ );
return $cache;
@@ -473,7 +468,7 @@ class MessageCache {
/**
* Updates cache as necessary when message page is changed
*
- * @param $title String: name of the page changed.
+ * @param string $title name of the page changed.
* @param $text Mixed: new contents of the page.
*/
public function replace( $title, $text ) {
@@ -512,7 +507,7 @@ class MessageCache {
// Also delete cached sidebar... just in case it is affected
$codes = array( $code );
- if ( $code === 'en' ) {
+ if ( $code === 'en' ) {
// Delete all sidebars, like for example on action=purge on the
// sidebar messages
$codes = array_keys( Language::fetchLanguageNames() );
@@ -536,9 +531,9 @@ class MessageCache {
/**
* Shortcut to update caches.
*
- * @param $cache Array: cached messages with a version.
- * @param $memc Bool: Wether to update or not memcache.
- * @param $code String: Language code.
+ * @param array $cache cached messages with a version.
+ * @param bool $memc Wether to update or not memcache.
+ * @param string $code Language code.
* @return bool on somekind of error.
*/
protected function saveToCaches( $cache, $memc = true, $code = false ) {
@@ -558,7 +553,7 @@ class MessageCache {
$serialized = serialize( $cache );
$hash = md5( $serialized );
$this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
- if ($wgLocalMessageCacheSerialized) {
+ if ( $wgLocalMessageCacheSerialized ) {
$this->saveToLocal( $serialized, $hash, $code );
} else {
$this->saveToScript( $cache, $hash, $code );
@@ -593,10 +588,10 @@ class MessageCache {
/**
* Get a message from either the content language or the user language.
*
- * @param $key String: the message cache key
+ * @param string $key the message cache key
* @param $useDB Boolean: get the message from the DB, false to use only
* the localisation
- * @param $langcode String: code of the language to get the message for, if
+ * @param bool|string $langcode Code of the language to get the message for, if
* it is a valid code create a language for that language,
* if it is a string but not a valid code then make a basic
* language object, if it is a false boolean then use the
@@ -607,6 +602,7 @@ class MessageCache {
* @param $isFullKey Boolean: specifies whether $key is a two part key
* "msg/lang".
*
+ * @throws MWException
* @return string|bool
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
@@ -645,13 +641,6 @@ class MessageCache {
$uckey = $wgContLang->ucfirst( $lckey );
}
- /**
- * Record each message request, but only once per request.
- * This information is not used unless $wgAdaptiveMessageCache
- * is enabled.
- */
- $this->mRequestedMessages[$uckey] = true;
-
# Try the MediaWiki namespace
if( !$this->mDisable && $useDB ) {
$title = $uckey;
@@ -712,14 +701,12 @@ class MessageCache {
* Get a message from the MediaWiki namespace, with caching. The key must
* first be converted to two-part lang/msg form if necessary.
*
- * @param $title String: Message cache key with initial uppercase letter.
- * @param $code String: code denoting the language to try.
+ * @param string $title Message cache key with initial uppercase letter.
+ * @param string $code code denoting the language to try.
*
* @return string|bool False on failure
*/
function getMsgFromNamespace( $title, $code ) {
- global $wgAdaptiveMessageCache;
-
$this->load( $code );
if ( isset( $this->mCache[$code][$title] ) ) {
$entry = $this->mCache[$code][$title];
@@ -738,15 +725,7 @@ class MessageCache {
return $message;
}
- /**
- * If message cache is in normal mode, it is guaranteed
- * (except bugs) that there is always entry (or placeholder)
- * in the cache if message exists. Thus we can do minor
- * performance improvement and return false early.
- */
- if ( !$wgAdaptiveMessageCache ) {
- return false;
- }
+ return false;
}
# Try the individual message cache
@@ -770,16 +749,32 @@ class MessageCache {
Title::makeTitle( NS_MEDIAWIKI, $title ), false, Revision::READ_LATEST
);
if ( $revision ) {
- $message = $revision->getText();
- if ($message === false) {
+ $content = $revision->getContent();
+ if ( !$content ) {
// A possibly temporary loading failure.
wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title} ($code)" );
+ $message = null; // no negative caching
} else {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ // XXX: Is this the right way to turn a Content object into a message?
+ // NOTE: $content is typically either WikitextContent, JavaScriptContent or CssContent.
+ // MessageContent is *not* used for storing messages, it's only used for wrapping them when needed.
+ $message = $content->getWikitextForTransclusion();
+
+ if ( $message === false || $message === null ) {
+ wfDebugLog( 'MessageCache', __METHOD__ . ": message content doesn't provide wikitext "
+ . "(content model: " . $content->getContentHandler() . ")" );
+
+ $message = false; // negative caching
+ } else {
+ $this->mCache[$code][$title] = ' ' . $message;
+ $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ }
}
} else {
- $message = false;
+ $message = false; // negative caching
+ }
+
+ if ( $message === false ) { // negative caching
$this->mCache[$code][$title] = '!NONEXISTENT';
$this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
}
@@ -845,7 +840,7 @@ class MessageCache {
* @param $linestart bool
* @param $interface bool
* @param $language
- * @return ParserOutput
+ * @return ParserOutput|string
*/
public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null ) {
if ( $this->mInParser ) {
@@ -890,7 +885,7 @@ class MessageCache {
*/
function clear() {
$langs = Language::fetchLanguageNames( null, 'mw' );
- foreach ( array_keys($langs) as $code ) {
+ foreach ( array_keys( $langs ) as $code ) {
# Global cache
$this->mMemc->delete( wfMemcKey( 'messages', $code ) );
# Invalidate all local caches
@@ -919,82 +914,6 @@ class MessageCache {
return array( $message, $lang );
}
- public static function logMessages() {
- wfProfileIn( __METHOD__ );
- global $wgAdaptiveMessageCache;
- if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $cachekey = wfMemckey( 'message-profiling' );
- $cache = wfGetCache( CACHE_DB );
- $data = $cache->get( $cachekey );
-
- if ( !$data ) {
- $data = array();
- }
-
- $age = self::$mAdaptiveDataAge;
- $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
- foreach ( array_keys( $data ) as $key ) {
- if ( $key < $filterDate ) {
- unset( $data[$key] );
- }
- }
-
- $index = substr( wfTimestampNow(), 0, 8 );
- if ( !isset( $data[$index] ) ) {
- $data[$index] = array();
- }
-
- foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
- if ( !isset( $data[$index][$message] ) ) {
- $data[$index][$message] = 0;
- }
- $data[$index][$message]++;
- }
-
- $cache->set( $cachekey, $data );
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * @return array
- */
- public function getMostUsedMessages() {
- wfProfileIn( __METHOD__ );
- $cachekey = wfMemcKey( 'message-profiling' );
- $cache = wfGetCache( CACHE_DB );
- $data = $cache->get( $cachekey );
- if ( !$data ) {
- wfProfileOut( __METHOD__ );
- return array();
- }
-
- $list = array();
-
- foreach( $data as $messages ) {
- foreach( $messages as $message => $count ) {
- $key = $message;
- if ( !isset( $list[$key] ) ) {
- $list[$key] = 0;
- }
- $list[$key] += $count;
- }
- }
-
- $max = max( $list );
- foreach ( $list as $message => $count ) {
- if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
- unset( $list[$message] );
- }
- }
-
- wfProfileOut( __METHOD__ );
- return array_keys( $list );
- }
-
/**
* Get all message keys stored in the message cache for a given language.
* If $code is the content language code, this will return all message keys