const LOCK_TTL = 30;
/**
- * Process local cache of loaded messages that are defined in
- * MediaWiki namespace. First array level is a language code,
- * second level is message key and the values are either message
- * content prefixed with space, or !NONEXISTENT for negative
- * caching.
- * @var array $mCache
+ * Process cache of loaded messages that are defined in MediaWiki namespace
+ *
+ * @var MapCacheLRU Map of (language code => key => " <MESSAGE>" or "!TOO BIG")
*/
- protected $mCache;
+ protected $cache;
/**
* @var bool[] Map of (language code => boolean)
/** @var Parser */
protected $mParser;
- /**
- * Variable for tracking which variables are already loaded
- * @var array $mLoadedLanguages
- */
- protected $mLoadedLanguages = [];
-
/**
* @var bool $mInParser
*/
$this->clusterCache = $clusterCache;
$this->srvCache = $serverCache;
+ $this->cache = new MapCacheLRU( 5 ); // limit size for sanity
+
$this->mDisable = !$useDB;
$this->mExpiry = $expiry;
}
*
* @param string $code Language to which load messages
* @param int $mode Use MessageCache::FOR_UPDATE to skip process cache [optional]
- * @throws MWException
+ * @throws InvalidArgumentException
* @return bool
*/
protected function load( $code, $mode = null ) {
}
# Don't do double loading...
- if ( isset( $this->mLoadedLanguages[$code] ) && $mode != self::FOR_UPDATE ) {
+ if ( $this->cache->has( $code ) && $mode != self::FOR_UPDATE ) {
return true;
}
$staleCache = $cache;
} else {
$where[] = 'got from local cache';
+ $this->cache->set( $code, $cache );
$success = true;
- $this->mCache[$code] = $cache;
}
if ( !$success ) {
$staleCache = $cache;
} else {
$where[] = 'got from global cache';
- $this->mCache[$code] = $cache;
+ $this->cache->set( $code, $cache );
$this->saveToCaches( $cache, 'local-only', $code );
$success = true;
}
} elseif ( $staleCache ) {
# Use the stale cache while some other thread constructs the new one
$where[] = 'using stale cache';
- $this->mCache[$code] = $staleCache;
+ $this->cache->set( $code, $staleCache );
$success = true;
break;
} elseif ( $failedAttempts > 0 ) {
if ( !$success ) {
$where[] = 'loading FAILED - cache is disabled';
$this->mDisable = true;
- $this->mCache = false;
+ $this->cache->set( $code, null );
wfDebugLog( 'MessageCacheError', __METHOD__ . ": Failed to load $code\n" );
# This used to throw an exception, but that led to nasty side effects like
# the whole wiki being instantly down if the memcached server died
- } else {
- # All good, just record the success
- $this->mLoadedLanguages[$code] = true;
+ }
+
+ if ( !$this->cache->has( $code ) ) { // sanity
+ throw new LogicException( "Process cache for '$code' should be set by now." );
}
$info = implode( ', ', $where );
}
$cache = $this->loadFromDB( $code, $mode );
- $this->mCache[$code] = $cache;
+ $this->cache->set( $code, $cache );
$saveSuccess = $this->saveToCaches( $cache, 'all', $code );
if ( !$saveSuccess ) {
$mostused = [];
if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
- if ( !isset( $this->mCache[$wgLanguageCode] ) ) {
+ if ( !$this->cache->has( $wgLanguageCode ) ) {
$this->load( $wgLanguageCode );
}
- $mostused = array_keys( $this->mCache[$wgLanguageCode] );
+ $mostused = array_keys( $this->cache->get( $wgLanguageCode ) );
foreach ( $mostused as $key => $value ) {
$mostused[$key] = "$value/$code";
}
// (a) Update the process cache with the new message text
if ( $text === false ) {
// Page deleted
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
} else {
// Ignore $wgMaxMsgCacheEntrySize so the process cache is up to date
- $this->mCache[$code][$title] = ' ' . $text;
+ $this->cache->setField( $code, $title, ' ' . $text );
}
// (b) Update the shared caches in a deferred update with a fresh DB snapshot
}
// Load the messages from the master DB to avoid race conditions
$cache = $this->loadFromDB( $code, self::FOR_UPDATE );
- $this->mCache[$code] = $cache;
- // Load the process cache values and set the per-title cache keys
+ // Check if an individual cache key should exist and update cache accordingly
$page = WikiPage::factory( Title::makeTitle( NS_MEDIAWIKI, $title ) );
$page->loadPageData( $page::READ_LATEST );
$text = $this->getMessageTextFromContent( $page->getContent() );
- // Check if an individual cache key should exist and update cache accordingly
if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
- $titleKey = $this->bigMessageCacheKey( $this->mCache[$code]['HASH'], $title );
- $this->wanCache->set( $titleKey, ' ' . $text, $this->mExpiry );
+ $this->wanCache->set(
+ $this->bigMessageCacheKey( $cache['HASH'], $title ),
+ ' ' . $text,
+ $this->mExpiry
+ );
}
// Mark this cache as definitely being "latest" (non-volatile) so
- // load() calls do try to refresh the cache with replica DB data
- $this->mCache[$code]['LATEST'] = time();
+ // load() calls do not try to refresh the cache with replica DB data
+ $cache['LATEST'] = time();
+ // Update the process cache
+ $this->cache->set( $code, $cache );
// Pre-emptively update the local datacenter cache so things like edit filter and
// blacklist changes are reflected immediately; these often use MediaWiki: pages.
// The datacenter handling replace() calls should be the same one handling edits
// as they require HTTP POST.
- $this->saveToCaches( $this->mCache[$code], 'all', $code );
+ $this->saveToCaches( $cache, 'all', $code );
// Release the lock now that the cache is saved
ScopedCallback::consume( $scopedLock );
public function getMsgFromNamespace( $title, $code ) {
$this->load( $code );
- if ( isset( $this->mCache[$code][$title] ) ) {
- $entry = $this->mCache[$code][$title];
+ if ( $this->cache->hasField( $code, $title ) ) {
+ $entry = $this->cache->getField( $code, $title );
if ( substr( $entry, 0, 1 ) === ' ' ) {
// The message exists and is not '!TOO BIG'
return (string)substr( $entry, 1 );
$message = false;
Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
if ( $message !== false ) {
- $this->mCache[$code][$title] = ' ' . $message;
+ $this->cache->setField( $code, $title, ' ' . $message );
} else {
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
}
return $message;
}
// Individual message cache key
- $titleKey = $this->bigMessageCacheKey( $this->mCache[$code]['HASH'], $title );
+ $bigKey = $this->bigMessageCacheKey( $this->cache->getField( $code, 'HASH' ), $title );
if ( $this->mCacheVolatile[$code] ) {
$entry = false;
// Make sure that individual keys respect the WAN cache holdoff period too
LoggerFactory::getInstance( 'MessageCache' )->debug(
__METHOD__ . ': loading volatile key \'{titleKey}\'',
- [ 'titleKey' => $titleKey, 'code' => $code ] );
+ [ 'titleKey' => $bigKey, 'code' => $code ] );
} else {
// Try the individual message cache
- $entry = $this->wanCache->get( $titleKey );
+ $entry = $this->wanCache->get( $bigKey );
}
if ( $entry !== false ) {
if ( substr( $entry, 0, 1 ) === ' ' ) {
- $this->mCache[$code][$title] = $entry;
+ $this->cache->setField( $code, $title, $entry );
// The message exists, so make sure a string is returned
return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
- $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
return false;
} else {
// Corrupt/obsolete entry, delete it
- $this->wanCache->delete( $titleKey );
+ $this->wanCache->delete( $bigKey );
}
}
if ( $content ) {
$message = $this->getMessageTextFromContent( $content );
if ( is_string( $message ) ) {
- $this->mCache[$code][$title] = ' ' . $message;
- $this->wanCache->set( $titleKey, ' ' . $message, $this->mExpiry, $cacheOpts );
+ $this->cache->setField( $code, $title, ' ' . $message );
+ $this->wanCache->set( $bigKey, ' ' . $message, $this->mExpiry, $cacheOpts );
}
} else {
// A possibly temporary loading failure
LoggerFactory::getInstance( 'MessageCache' )->warning(
__METHOD__ . ': failed to load message page text for \'{titleKey}\'',
- [ 'titleKey' => $titleKey, 'code' => $code ] );
+ [ 'titleKey' => $bigKey, 'code' => $code ] );
$message = null; // no negative caching
}
} else {
if ( $message === false ) {
// Negative caching in case a "too big" message is no longer available (deleted)
- $this->mCache[$code][$title] = '!NONEXISTENT';
- $this->wanCache->set( $titleKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts );
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
+ $this->wanCache->set( $bigKey, '!NONEXISTENT', $this->mExpiry, $cacheOpts );
}
return $message;
*
* Mainly used after a mass rebuild
*/
- function clear() {
+ public function clear() {
$langs = Language::fetchLanguageNames( null, 'mw' );
foreach ( array_keys( $langs ) as $code ) {
$this->wanCache->touchCheckKey( $this->getCheckKey( $code ) );
}
-
- $this->mLoadedLanguages = [];
+ $this->cache->clear();
}
/**
global $wgContLang;
$this->load( $code );
- if ( !isset( $this->mCache[$code] ) ) {
+ if ( !$this->cache->has( $code ) ) {
// Apparently load() failed
return null;
}
// Remove administrative keys
- $cache = $this->mCache[$code];
+ $cache = $this->cache->get( $code );
unset( $cache['VERSION'] );
unset( $cache['EXPIRY'] );
unset( $cache['EXCESSIVE'] );