- // Update caches if the lock was acquired
- if ( $scopedLock ) {
- $this->saveToCaches( $this->mCache[$code], 'all', $code );
- } else {
- LoggerFactory::getInstance( 'MessageCache' )->error(
- __METHOD__ . ': could not acquire lock to update {title} ({code})',
- [ 'title' => $title, 'code' => $code ] );
- }
-
- ScopedCallback::consume( $scopedLock );
- // Relay the purge to APC and other DCs
- $this->wanCache->touchCheckKey( wfMemcKey( 'messages', $code ) );
-
- // Also delete cached sidebar... just in case it is affected
- $codes = [ $code ];
- if ( $code === 'en' ) {
- // Delete all sidebars, like for example on action=purge on the
- // sidebar messages
- $codes = array_keys( Language::fetchLanguageNames() );
- }
-
- foreach ( $codes as $code ) {
- $sidebarKey = wfMemcKey( 'sidebar', $code );
- $this->wanCache->delete( $sidebarKey );
- }
+ // (b) Update the shared caches in a deferred update with a fresh DB snapshot
+ DeferredUpdates::addCallableUpdate(
+ function () use ( $title, $msg, $code ) {
+ global $wgContLang, $wgMaxMsgCacheEntrySize;
+ // Allow one caller at a time to avoid race conditions
+ $scopedLock = $this->getReentrantScopedLock( wfMemcKey( 'messages', $code ) );
+ if ( !$scopedLock ) {
+ LoggerFactory::getInstance( 'MessageCache' )->error(
+ __METHOD__ . ': could not acquire lock to update {title} ({code})',
+ [ 'title' => $title, 'code' => $code ] );
+ return;
+ }
+ // 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
+ $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
+ $titleKey = $this->wanCache->makeKey(
+ 'messages-big', $this->mCache[$code]['HASH'], $title );
+ if ( is_string( $text ) && strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
+ $this->wanCache->set( $titleKey, ' ' . $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();
+ // Pre-emptively update the local datacenter cache so things like edit filter and
+ // blacklist changes are reflect immediately, as 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 );
+ // Release the lock now that the cache is saved
+ ScopedCallback::consume( $scopedLock );
+
+ // Relay the purge. Touching this check key expires cache contents
+ // and local cache (APC) validation hash across all datacenters.
+ $this->wanCache->touchCheckKey( wfMemcKey( 'messages', $code ) );
+ // Also delete cached sidebar... just in case it is affected
+ // @TODO: shouldn't this be $code === $wgLanguageCode?
+ if ( $code === 'en' ) {
+ // Purge all language sidebars, e.g. on ?action=purge to the sidebar messages
+ $codes = array_keys( Language::fetchLanguageNames() );
+ } else {
+ // Purge only the sidebar for this language
+ $codes = [ $code ];
+ }
+ foreach ( $codes as $code ) {
+ $this->wanCache->delete( wfMemcKey( 'sidebar', $code ) );
+ }