r35821 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r35820‎ | r35821 | r35822 >
Date:20:41, 3 June 2008
Author:nikerabbit
Status:old
Tags:
Comment:
* Second try of split message caches with changes suggested by Tim Starling and other small fixes
* removed use of umask, wfMkdirParents works now
* splitting of messages per language is now unconditional
* storing cached messages in 2-d array of code => messages
* fixed a bug in replace(), now deletion works properly
* clear() should actually do something now
* try to delay parser creation in transform()
* removed the use of $memcPrefix and wfMemcKeyLang() in favour of wfMemcKey()
* escape $code in loadFromDb()
* use member variable instead of static variable for tracking loaded languages
Modified paths:
  • /trunk/phase3/includes/MessageCache.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/MessageCache.php
@@ -18,35 +18,34 @@
1919 * @ingroup Cache
2020 */
2121 class MessageCache {
22 - var $mCache, $mUseCache, $mDisable, $mExpiry;
23 - var $mMemcKey, $mKeys, $mParserOptions, $mParser;
 22+ // Holds loaded messages that are defined in MediaWiki namespace.
 23+ var $mCache;
 24+
 25+ var $mUseCache, $mDisable, $mExpiry;
 26+ var $mKeys, $mParserOptions, $mParser;
2427 var $mExtensionMessages = array();
2528 var $mInitialised = false;
26 - var $mDeferred = true;
27 - var $mAllMessagesLoaded;
 29+ var $mAllMessagesLoaded; // Extension messages
2830
29 - function __construct( &$memCached, $useDB, $expiry, $memcPrefix) {
30 - wfProfileIn( __METHOD__ );
 31+ // Variable for tracking which variables are loaded
 32+ var $mLoadedLanguages = array();
3133
 34+ function __construct( &$memCached, $useDB, $expiry, /*ignored*/ $memcPrefix ) {
3235 $this->mUseCache = !is_null( $memCached );
3336 $this->mMemc = &$memCached;
3437 $this->mDisable = !$useDB;
3538 $this->mExpiry = $expiry;
3639 $this->mDisableTransform = false;
37 - $this->mMemcKey = $memcPrefix.':messages';
3840 $this->mKeys = false; # initialised on demand
3941 $this->mInitialised = true;
4042 $this->mParser = null;
 43+ }
4144
42 - # When we first get asked for a message,
43 - # then we'll fill up the cache. If we
44 - # can return a cache hit, this saves
45 - # some extra milliseconds
46 - $this->mDeferred = true;
4745
48 - wfProfileOut( __METHOD__ );
49 - }
50 -
 46+ /**
 47+ * ParserOptions is lazy initialised.
 48+ * Access should probably be protected.
 49+ */
5150 function getParserOptions() {
5251 if ( !$this->mParserOptions ) {
5352 $this->mParserOptions = new ParserOptions;
@@ -55,22 +54,25 @@
5655 }
5756
5857 /**
59 - * Try to load the cache from a local file
 58+ * Try to load the cache from a local file.
 59+ * Actual format of the file depends on the $wgLocalMessageCacheSerialized
 60+ * setting.
 61+ *
 62+ * @param $hash String: the hash of contents, to check validity.
 63+ * @param $code Mixed: Optional language code, see documenation of load().
 64+ * @return false on failure.
6065 */
61 - function loadFromLocal( $hash ) {
 66+ function loadFromLocal( $hash, $code ) {
6267 global $wgLocalMessageCache, $wgLocalMessageCacheSerialized;
6368
64 - if ( $wgLocalMessageCache === false ) {
65 - return;
66 - }
 69+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code";
6770
68 - $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
69 -
 71+ # Check file existence
7072 wfSuppressWarnings();
7173 $file = fopen( $filename, 'r' );
7274 wfRestoreWarnings();
7375 if ( !$file ) {
74 - return;
 76+ return false; // No cache file
7577 }
7678
7779 if ( $wgLocalMessageCacheSerialized ) {
@@ -82,37 +84,38 @@
8385 while ( !feof( $file ) ) {
8486 $serialized .= fread( $file, 100000 );
8587 }
86 - $this->setCache( unserialize( $serialized ) );
 88+ fclose( $file );
 89+ return $this->setCache( unserialize( $serialized ), $code );
 90+ } else {
 91+ fclose( $file );
 92+ return false; // Wrong hash
8793 }
88 - fclose( $file );
8994 } else {
9095 $localHash=substr(fread($file,40),8);
9196 fclose($file);
9297 if ($hash!=$localHash) {
93 - return;
 98+ return false; // Wrong hash
9499 }
95100
96 - require("$wgLocalMessageCache/messages-" . wfWikiID());
97 - $this->setCache( $this->mCache);
 101+ # Require overwrites the member variable or just shadows it?
 102+ require( $filename );
 103+ return $this->setCache( $this->mCache, $code );
98104 }
99105 }
100106
101107 /**
102 - * Save the cache to a local file
 108+ * Save the cache to a local file.
103109 */
104 - function saveToLocal( $serialized, $hash ) {
105 - global $wgLocalMessageCache, $wgLocalMessageCacheSerialized;
 110+ function saveToLocal( $serialized, $hash, $code ) {
 111+ global $wgLocalMessageCache;
106112
107 - if ( $wgLocalMessageCache === false ) {
108 - return;
109 - }
 113+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code";
 114+ wfMkdirParents( $wgLocalMessageCache, 0777 ); // might fail
110115
111 - $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
112 - $oldUmask = umask( 0 );
113 - wfMkdirParents( $wgLocalMessageCache, 0777 );
114 - umask( $oldUmask );
 116+ wfSuppressWarnings();
 117+ $file = fopen( $filename, 'w' );
 118+ wfRestoreWarnings();
115119
116 - $file = fopen( $filename, 'w' );
117120 if ( !$file ) {
118121 wfDebug( "Unable to open local cache file for writing\n" );
119122 return;
@@ -123,32 +126,33 @@
124127 @chmod( $filename, 0666 );
125128 }
126129
127 - function loadFromScript( $hash ) {
128 - wfDeprecated( __METHOD__ );
129 - $this->loadFromLocal( $hash );
130 - }
 130+ function saveToScript( $array, $hash, $code ) {
 131+ global $wgLocalMessageCache;
131132
132 - function saveToScript($array, $hash) {
133 - global $wgLocalMessageCache;
134 - if ( $wgLocalMessageCache === false ) {
 133+ $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code";
 134+ $tempFilename = $filename . '.tmp';
 135+ wfMkdirParents( $wgLocalMessageCache, 0777 ); // might fail
 136+
 137+ wfSuppressWarnings();
 138+ $file = fopen( $tempFilename, 'w');
 139+ wfRestoreWarnings();
 140+
 141+ if ( !$file ) {
 142+ wfDebug( "Unable to open local cache file for writing\n" );
135143 return;
136144 }
137145
138 - $filename = "$wgLocalMessageCache/messages-" . wfWikiID();
139 - $oldUmask = umask( 0 );
140 - wfMkdirParents( $wgLocalMessageCache, 0777 );
141 - umask( $oldUmask );
142 - $file = fopen( $filename.'.tmp', 'w');
143146 fwrite($file,"<?php\n//$hash\n\n \$this->mCache = array(");
144147
145148 foreach ($array as $key => $message) {
146 - fwrite($file, "'". $this->escapeForScript($key).
147 - "' => '" . $this->escapeForScript($message).
148 - "',\n");
 149+ $key = $this->escapeForScript($key);
 150+ $messages = $this->escapeForScript($message);
 151+ fwrite($file, "'$key' => '$message',\n");
149152 }
 153+
150154 fwrite($file,");\n?>");
151155 fclose($file);
152 - rename($filename.'.tmp',$filename);
 156+ rename($tempFilename, $filename);
153157 }
154158
155159 function escapeForScript($string) {
@@ -160,222 +164,307 @@
161165 /**
162166 * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
163167 */
164 - function setCache( $cache ) {
 168+ function setCache( $cache, $code ) {
165169 if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
166 - $this->mCache = $cache;
 170+ $this->mCache[$code] = $cache;
 171+ return true;
167172 } else {
168 - $this->mCache = false;
 173+ return false;
169174 }
170175 }
171176
172177 /**
173 - * Loads messages either from memcached or the database, if not disabled
174 - * On error, quietly switches to a fallback mode
175 - * Returns false for a reportable error, true otherwise
 178+ * Loads messages from caches or from database in this order:
 179+ * (1) local message cache (if $wgLocalMessageCache is enabled)
 180+ * (2) memcached
 181+ * (3) from the database.
 182+ *
 183+ * When succesfully loading from (2) or (3), all higher level caches are
 184+ * updated for the newest version.
 185+ *
 186+ * Nothing is loaded if member variable mDisabled is true, either manually
 187+ * set by calling code or if message loading fails (is this possible?).
 188+ *
 189+ * Returns true if cache is already populated or it was succesfully populated,
 190+ * or false if populating empty cache fails. Also returns true if MessageCache
 191+ * is disabled.
 192+ *
 193+ * @param $code String: language to which load messages
176194 */
177 - function load() {
178 - global $wgLocalMessageCache, $wgLocalMessageCacheSerialized;
 195+ function load( $code = false ) {
 196+ global $wgLocalMessageCache;
179197
 198+ if ( !$this->mUseCache ) {
 199+ return true;
 200+ }
 201+
 202+ if( !is_string( $code ) ) {
 203+ # This isn't really nice, so at least make a note about it and try to
 204+ # fall back
 205+ wfDebug( __METHOD__ . " called without providing a language code\n" );
 206+ $code = 'en';
 207+ }
 208+
 209+ # Don't do double loading...
 210+ if ( isset($this->mLoadedLanguages[$code]) ) return true;
 211+
 212+ # 8 lines of code just to say (once) that message cache is disabled
180213 if ( $this->mDisable ) {
181214 static $shownDisabled = false;
182215 if ( !$shownDisabled ) {
183 - wfDebug( "MessageCache::load(): disabled\n" );
 216+ wfDebug( __METHOD__ . ": disabled\n" );
184217 $shownDisabled = true;
185218 }
186219 return true;
187220 }
188 - if ( !$this->mUseCache ) {
189 - $this->mDeferred = false;
190 - return true;
191 - }
192221
193 - $fname = 'MessageCache::load';
194 - wfProfileIn( $fname );
195 - $success = true;
 222+ # Loading code starts
 223+ wfProfileIn( __METHOD__ );
 224+ $success = false; # Keep track of success
 225+ $where = array(); # Debug info, delayed to avoid spamming debug log too much
 226+ $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
196227
197 - $this->mCache = false;
198228
199 - # Try local cache
 229+ # (1) local cache
 230+ # Hash of the contents is stored in memcache, to detect if local cache goes
 231+ # out of date (due to update in other thread?)
200232 if ( $wgLocalMessageCache !== false ) {
201 - wfProfileIn( $fname.'-fromlocal' );
202 - $hash = $this->mMemc->get( "{$this->mMemcKey}-hash" );
 233+ wfProfileIn( __METHOD__ . '-fromlocal' );
 234+
 235+ $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
203236 if ( $hash ) {
204 - $this->loadFromLocal( $hash );
205 - if ( $this->mCache ) {
206 - wfDebug( "MessageCache::load(): got from local cache\n" );
207 - }
 237+ $success = $this->loadFromLocal( $hash, $code );
 238+ if ( $success ) $where[] = 'got from local cache';
208239 }
209 - wfProfileOut( $fname.'-fromlocal' );
 240+ wfProfileOut( __METHOD__ . '-fromlocal' );
210241 }
211242
212 - # Try memcached
213 - if ( !$this->mCache ) {
214 - wfProfileIn( $fname.'-fromcache' );
215 - $this->setCache( $this->mMemc->get( $this->mMemcKey ) );
216 - if ( $this->mCache ) {
217 - wfDebug( "MessageCache::load(): got from global cache\n" );
218 - # Save to local cache
219 - if ( $wgLocalMessageCache !== false ) {
220 - $serialized = serialize( $this->mCache );
221 - if ( !$hash ) {
222 - $hash = md5( $serialized );
223 - $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry );
224 - }
225 - if ($wgLocalMessageCacheSerialized) {
226 - $this->saveToLocal( $serialized,$hash );
227 - } else {
228 - $this->saveToScript( $this->mCache, $hash );
229 - }
230 - }
 243+ # (2) memcache
 244+ # Fails if nothing in cache, or in the wrong version.
 245+ if ( !$success ) {
 246+ wfProfileIn( __METHOD__ . '-fromcache' );
 247+ $cache = $this->mMemc->get( $cacheKey );
 248+ $success = $this->setCache( $cache, $code );
 249+ if ( $success ) {
 250+ $where[] = 'got from global cache';
 251+ $this->saveToCaches( $cache, false, $code );
231252 }
232 - wfProfileOut( $fname.'-fromcache' );
 253+ wfProfileOut( __METHOD__ . '-fromcache' );
233254 }
234255
235256
236 - # If there's nothing in memcached, load all the messages from the database
237 - if ( !$this->mCache ) {
238 - wfDebug( "MessageCache::load(): cache is empty\n" );
239 - $this->lock();
240 - # Other threads don't need to load the messages if another thread is doing it.
241 - $success = $this->mMemc->add( $this->mMemcKey.'-status', "loading", MSG_LOAD_TIMEOUT );
242 - if ( $success ) {
243 - wfProfileIn( $fname.'-load' );
244 - wfDebug( "MessageCache::load(): loading all messages from DB\n" );
245 - $this->loadFromDB();
246 - wfProfileOut( $fname.'-load' );
 257+ # (3)
 258+ # Nothing in caches... so we need create one and store it in caches
 259+ if ( !$success ) {
 260+ $where[] = 'cache is empty';
 261+ $where[] = 'loading from database';
247262
248 - # Save in memcached
249 - # Keep trying if it fails, this is kind of important
250 - wfProfileIn( $fname.'-save' );
251 - for ($i=0; $i<20 &&
252 - !$this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
253 - $i++ ) {
254 - usleep(mt_rand(500000,1500000));
255 - }
 263+ $this->lock($cacheKey);
256264
257 - # Save to local cache
258 - if ( $wgLocalMessageCache !== false ) {
259 - $serialized = serialize( $this->mCache );
260 - $hash = md5( $serialized );
261 - $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry );
262 - if ($wgLocalMessageCacheSerialized) {
263 - $this->saveToLocal( $serialized,$hash );
264 - } else {
265 - $this->saveToScript( $this->mCache, $hash );
266 - }
267 - }
 265+ $cache = $this->loadFromDB( $code );
 266+ $success = $this->setCache( $cache, $code );
 267+ if ( $success ) {
 268+ $this->saveToCaches( $cache, true, $code );
 269+ }
268270
269 - wfProfileOut( $fname.'-save' );
270 - if ( $i == 20 ) {
271 - $this->mMemc->set( $this->mMemcKey.'-status', 'error', 60*5 );
272 - wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
273 - } else {
274 - $this->mMemc->delete( $this->mMemcKey.'-status' );
275 - }
276 - }
277 - $this->unlock();
 271+ $this->unlock($cacheKey);
278272 }
279273
280 - if ( !is_array( $this->mCache ) ) {
281 - wfDebug( "MessageCache::load(): unable to load cache, disabled\n" );
 274+ if ( !$success ) {
 275+ # Bad luck... this should not happen
 276+ $where[] = 'loading FAILED - cache is disabled';
 277+ $info = implode( ', ', $where );
 278+ wfDebug( __METHOD__ . ": Loading $code... $info\n" );
282279 $this->mDisable = true;
283280 $this->mCache = false;
 281+ } else {
 282+ # All good, just record the success
 283+ $info = implode( ', ', $where );
 284+ wfDebug( __METHOD__ . ": Loading $code... $info\n" );
 285+ $this->mLoadedLanguages[$code] = true;
284286 }
285 - wfProfileOut( $fname );
286 - $this->mDeferred = false;
 287+ wfProfileOut( __METHOD__ );
287288 return $success;
288289 }
289290
290291 /**
291 - * Loads all or main part of cacheable messages from the database
 292+ * Loads cacheable messages from the database. Messages bigger than
 293+ * $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
 294+ * on-demand from the database later.
 295+ *
 296+ * @param $code Optional language code, see documenation of load().
 297+ * @return Array: Loaded messages for storing in caches.
292298 */
293 - function loadFromDB() {
294 - global $wgMaxMsgCacheEntrySize;
295 -
 299+ function loadFromDB( $code = false ) {
296300 wfProfileIn( __METHOD__ );
 301+ global $wgMaxMsgCacheEntrySize, $wgContLanguageCode;
297302 $dbr = wfGetDB( DB_SLAVE );
298 - $this->mCache = array();
 303+ $cache = array();
299304
 305+ # Common conditions
 306+ $conds = array(
 307+ 'page_is_redirect' => 0,
 308+ 'page_namespace' => NS_MEDIAWIKI,
 309+ );
 310+
 311+ if ( $code ) {
 312+ # Is this fast enough. Should not matter if the filtering is done in the
 313+ # database or in code.
 314+ if ( $code !== $wgContLanguageCode ) {
 315+ # Messages for particular language
 316+ $escapedCode = $dbr->escapeLike( $code );
 317+ $conds[] = "page_title like '%%/$escapedCode'";
 318+ } else {
 319+ # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
 320+ # other than language code.
 321+ $conds[] = "page_title not like '%%/%%'";
 322+ }
 323+ }
 324+
 325+ # Conditions to fetch oversized pages to ignore them
 326+ $bigConds = $conds;
 327+ $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
 328+
300329 # Load titles for all oversized pages in the MediaWiki namespace
301 - $res = $dbr->select( 'page', 'page_title',
302 - array(
303 - 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ),
304 - 'page_is_redirect' => 0,
305 - 'page_namespace' => NS_MEDIAWIKI,
306 - ),
307 - __METHOD__ );
 330+ $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ );
308331 while ( $row = $dbr->fetchObject( $res ) ) {
309 - $this->mCache[$row->page_title] = '!TOO BIG';
 332+ $cache[$row->page_title] = '!TOO BIG';
310333 }
311334 $dbr->freeResult( $res );
312335
313 - # Load text for the remaining pages
 336+ # Conditions to load the remaining pages with their contents
 337+ $smallConds = $conds;
 338+ $smallConds[] = 'page_latest=rev_id';
 339+ $smallConds[] = 'rev_text_id=old_id';
 340+ $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
 341+
314342 $res = $dbr->select( array( 'page', 'revision', 'text' ),
315343 array( 'page_title', 'old_text', 'old_flags' ),
316 - array(
317 - 'page_is_redirect' => 0,
318 - 'page_namespace' => NS_MEDIAWIKI,
319 - 'page_latest=rev_id',
320 - 'rev_text_id=old_id',
321 - 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ),
322 - __METHOD__ );
 344+ $smallConds, __METHOD__ );
323345
324346 for ( $row = $dbr->fetchObject( $res ); $row; $row = $dbr->fetchObject( $res ) ) {
325 - $this->mCache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
 347+ $cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
326348 }
327 - $this->mCache['VERSION'] = MSG_CACHE_VERSION;
328349 $dbr->freeResult( $res );
 350+
 351+ $cache['VERSION'] = MSG_CACHE_VERSION;
329352 wfProfileOut( __METHOD__ );
 353+ return $cache;
330354 }
331355
332 - function replace( $title, $text ) {
333 - global $wgLocalMessageCache, $wgLocalMessageCacheSerialized, $parserMemc;
 356+ /**
 357+ * Updates cache as necessary when message page is changed
 358+ *
 359+ * @param $title String: name of the page changed.
 360+ * @param $text Mixed: new contents of the page.
 361+ */
 362+ public function replace( $title, $text ) {
334363 global $wgMaxMsgCacheEntrySize;
 364+ wfProfileIn( __METHOD__ );
335365
336 - wfProfileIn( __METHOD__ );
337 - $this->lock();
338 - $this->load();
339 - if ( is_array( $this->mCache ) ) {
 366+
 367+ list( , $code ) = $this->figureMessage( $title );
 368+
 369+ $cacheKey = wfMemcKey( 'messages', $code );
 370+ $this->load($code);
 371+ $this->lock($cacheKey);
 372+
 373+ if ( is_array($this->mCache[$code]) ) {
 374+ $titleKey = wfMemcKey( 'messages', 'individual', $title );
 375+
340376 if ( $text === false ) {
341377 # Article was deleted
342 - unset( $this->mCache[$title] );
343 - $this->mMemc->delete( "$this->mMemcKey:{$title}" );
 378+ unset( $this->mCache[$code][$title] );
 379+ $this->mMemc->delete( $titleKey );
 380+
344381 } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
345 - $this->mCache[$title] = '!TOO BIG';
346 - $this->mMemc->set( "$this->mMemcKey:{$title}", ' '.$text, $this->mExpiry );
 382+ # Check for size
 383+ $this->mCache[$code][$title] = '!TOO BIG';
 384+ $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
 385+
347386 } else {
348 - $this->mCache[$title] = ' ' . $text;
349 - $this->mMemc->delete( "$this->mMemcKey:{$title}" );
 387+ $this->mCache[$code][$title] = ' ' . $text;
 388+ $this->mMemc->delete( $titleKey );
350389 }
351 - $this->mMemc->set( $this->mMemcKey, $this->mCache, $this->mExpiry );
352390
353 - # Save to local cache
354 - if ( $wgLocalMessageCache !== false ) {
355 - $serialized = serialize( $this->mCache );
356 - $hash = md5( $serialized );
357 - $this->mMemc->set( "{$this->mMemcKey}-hash", $hash, $this->mExpiry );
358 - if ($wgLocalMessageCacheSerialized) {
359 - $this->saveToLocal( $serialized,$hash );
360 - } else {
361 - $this->saveToScript( $this->mCache, $hash );
362 - }
 391+ # Update caches
 392+ $this->saveToCaches( $this->mCache[$code], true, $code );
 393+ }
 394+ $this->unlock($cacheKey);
 395+
 396+ // Also delete cached sidebar... just in case it is affected
 397+ global $parserMemc;
 398+ $sidebarKey = wfMemcKey( 'sidebar', $code );
 399+ $parserMemc->delete( $sidebarKey );
 400+
 401+ wfProfileOut( __METHOD__ );
 402+ }
 403+
 404+ /**
 405+ * Shortcut to update caches.
 406+ *
 407+ * @param $cache Array: cached messages with a version.
 408+ * @param $cacheKey String: Identifier for the cache.
 409+ * @param $memc Bool: Wether to update or not memcache.
 410+ * @param $code String: Language code.
 411+ * @return False on somekind of error.
 412+ */
 413+ protected function saveToCaches( $cache, $memc = true, $code = false ) {
 414+ wfProfileIn( __METHOD__ );
 415+ global $wgLocalMessageCache, $wgLocalMessageCacheSerialized;
 416+
 417+ $cacheKey = wfMemcKey( 'messages', $code );
 418+ $statusKey = wfMemcKey( 'messages', $code, 'status' );
 419+
 420+ $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
 421+ if ( !$success ) return true; # Other process should be updating them now
 422+
 423+ $i = 0;
 424+ if ( $memc ) {
 425+ # Save in memcached
 426+ # Keep trying if it fails, this is kind of important
 427+
 428+ for ($i=0; $i<20 &&
 429+ !$this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
 430+ $i++ ) {
 431+ usleep(mt_rand(500000,1500000));
363432 }
364433 }
365 - $this->unlock();
366 - $parserMemc->delete(wfMemcKey('sidebar'));
 434+
 435+ # Save to local cache
 436+ if ( $wgLocalMessageCache !== false ) {
 437+ $serialized = serialize( $cache );
 438+ $hash = md5( $serialized );
 439+ $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
 440+ if ($wgLocalMessageCacheSerialized) {
 441+ $this->saveToLocal( $serialized, $hash, $code );
 442+ } else {
 443+ $this->saveToScript( $cache, $hash, $code );
 444+ }
 445+ }
 446+
 447+ if ( $i == 20 ) {
 448+ $this->mMemc->set( $statusKey, 'error', 60*5 );
 449+ wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
 450+ $success = false;
 451+ } else {
 452+ $this->mMemc->delete( $statusKey );
 453+ $success = true;
 454+ }
367455 wfProfileOut( __METHOD__ );
 456+ return $success;
368457 }
369458
370459 /**
371460 * Returns success
372461 * Represents a write lock on the messages key
373462 */
374 - function lock() {
 463+ function lock($key) {
375464 if ( !$this->mUseCache ) {
376465 return true;
377466 }
378467
379 - $lockKey = $this->mMemcKey . 'lock';
 468+ $lockKey = $key . ':lock';
380469 for ($i=0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
381470 sleep(1);
382471 }
@@ -383,12 +472,12 @@
384473 return $i >= MSG_WAIT_TIMEOUT;
385474 }
386475
387 - function unlock() {
 476+ function unlock($key) {
388477 if ( !$this->mUseCache ) {
389478 return;
390479 }
391480
392 - $lockKey = $this->mMemcKey . 'lock';
 481+ $lockKey = $key . ':lock';
393482 $this->mMemc->delete( $lockKey );
394483 }
395484
@@ -438,10 +527,6 @@
439528 if( !$this->mInitialised ) {
440529 return '&lt;' . htmlspecialchars($key) . '&gt;';
441530 }
442 - # If cache initialization was deferred, start it now.
443 - if( $this->mDeferred && !$this->mDisable && $useDB ) {
444 - $this->load();
445 - }
446531
447532 $message = false;
448533
@@ -455,10 +540,11 @@
456541 if(!$isFullKey && ($langcode != $wgContLanguageCode) ) {
457542 $title .= '/' . $langcode;
458543 }
459 - $message = $this->getMsgFromNamespace( $title );
 544+ $message = $this->getMsgFromNamespace( $title, $langcode );
460545 }
 546+
461547 # Try the extension array
462 - if( $message === false && isset( $this->mExtensionMessages[$langcode][$lckey] ) ) {
 548+ if ( $message === false && isset( $this->mExtensionMessages[$langcode][$lckey] ) ) {
463549 $message = $this->mExtensionMessages[$langcode][$lckey];
464550 }
465551 if ( $message === false && isset( $this->mExtensionMessages['en'][$lckey] ) ) {
@@ -466,11 +552,8 @@
467553 }
468554
469555 # Try the array in the language object
470 - if( $message === false ) {
471 - #wfDebug( "Trying language object for message $key\n" );
472 - wfSuppressWarnings();
 556+ if ( $message === false ) {
473557 $message = $lang->getMessage( $lckey );
474 - wfRestoreWarnings();
475558 if ( is_null( $message ) ) {
476559 $message = false;
477560 }
@@ -482,14 +565,14 @@
483566 $mkey = substr( $lckey, 0, $pos );
484567 $code = substr( $lckey, $pos+1 );
485568 if ( $code ) {
 569+ # We may get calls for things that are http-urls from sidebar
 570+ # Let's not load nonexistent languages for those
486571 $validCodes = array_keys( Language::getLanguageNames() );
487572 if ( in_array( $code, $validCodes ) ) {
488573 $message = Language::getMessageFor( $mkey, $code );
489574 if ( is_null( $message ) ) {
490575 $message = false;
491576 }
492 - } else {
493 - wfDebug( __METHOD__ . ": Invalid code $code for $mkey/$code, not trying messages array\n" );
494577 }
495578 }
496579 }
@@ -498,7 +581,7 @@
499582 if( ($message === false || $message === '-' ) &&
500583 !$this->mDisable && $useDB &&
501584 !$isFullKey && ($langcode != $wgContLanguageCode) ) {
502 - $message = $this->getMsgFromNamespace( $wgContLang->ucfirst( $lckey ) );
 585+ $message = $this->getMsgFromNamespace( $wgContLang->ucfirst( $lckey ), $wgContLanguageCode );
503586 }
504587
505588 # Final fallback
@@ -512,18 +595,21 @@
513596 * Get a message from the MediaWiki namespace, with caching. The key must
514597 * first be converted to two-part lang/msg form if necessary.
515598 *
516 - * @param string $title Message cache key with initial uppercase letter
 599+ * @param $title String: Message cache key with initial uppercase letter.
 600+ * @param $code String: code denoting the language to try.
517601 */
518 - function getMsgFromNamespace( $title ) {
 602+ function getMsgFromNamespace( $title, $code ) {
 603+ $type = false;
519604 $message = false;
520 - $type = false;
521605
522 - # Try the cache
523 - if( $this->mUseCache && isset( $this->mCache[$title] ) ) {
524 - $entry = $this->mCache[$title];
525 - $type = substr( $entry, 0, 1 );
526 - if ( $type == ' ' ) {
527 - return substr( $entry, 1 );
 606+ if ( $this->mUseCache ) {
 607+ $this->load( $code );
 608+ if (isset( $this->mCache[$code][$title] ) ) {
 609+ $entry = $this->mCache[$code][$title];
 610+ $type = substr( $entry, 0, 1 );
 611+ if ( $type == ' ' ) {
 612+ return substr( $entry, 1 );
 613+ }
528614 }
529615 }
530616
@@ -534,27 +620,28 @@
535621 }
536622
537623 # If there is no cache entry and no placeholder, it doesn't exist
538 - if ( $type != '!' && $message === false ) {
 624+ if ( $type !== '!' ) {
539625 return false;
540626 }
541627
542 - $memcKey = $this->mMemcKey . ':' . $title;
 628+ $titleKey = wfMemcKey( 'messages', 'individual', $title );
543629
544630 # Try the individual message cache
545631 if ( $this->mUseCache ) {
546 - $entry = $this->mMemc->get( $memcKey );
 632+ $entry = $this->mMemc->get( $titleKey );
547633 if ( $entry ) {
548634 $type = substr( $entry, 0, 1 );
549635
550 - if ( $type == ' ' ) {
 636+ if ( $type === ' ' ) {
 637+ # Ok!
551638 $message = substr( $entry, 1 );
552 - $this->mCache[$title] = $entry;
 639+ $this->mCache[$code][$title] = $entry;
553640 return $message;
554 - } elseif ( $entry == '!NONEXISTENT' ) {
 641+ } elseif ( $entry === '!NONEXISTENT' ) {
555642 return false;
556643 } else {
557644 # Corrupt/obsolete entry, delete it
558 - $this->mMemc->delete( $memcKey );
 645+ $this->mMemc->delete( $titleKey );
559646 }
560647
561648 }
@@ -565,40 +652,43 @@
566653 if( $revision ) {
567654 $message = $revision->getText();
568655 if ($this->mUseCache) {
569 - $this->mCache[$title] = ' ' . $message;
570 - $this->mMemc->set( $memcKey, $message, $this->mExpiry );
 656+ $this->mCache[$code][$title] = ' ' . $message;
 657+ $this->mMemc->set( $titleKey, $message, $this->mExpiry );
571658 }
572659 } else {
573660 # Negative caching
574661 # Use some special text instead of false, because false gets converted to '' somewhere
575 - $this->mMemc->set( $memcKey, '!NONEXISTENT', $this->mExpiry );
576 - $this->mCache[$title] = false;
 662+ $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
 663+ $this->mCache[$code][$title] = false;
577664 }
578 -
579665 return $message;
580666 }
581667
582668 function transform( $message, $interface = false ) {
 669+ // Avoid creating parser if nothing to transfrom
 670+ if( strpos( $message, '{{' ) === false ) {
 671+ return $message;
 672+ }
 673+
583674 global $wgParser;
584675 if ( !$this->mParser && isset( $wgParser ) ) {
585676 # Do some initialisation so that we don't have to do it twice
586677 $wgParser->firstCallInit();
587678 # Clone it and store it
588679 $this->mParser = clone $wgParser;
 680+ #wfDebug( __METHOD__ . ": following contents triggered transform: $message\n" );
589681 }
590682 if ( $this->mParser ) {
591 - if( strpos( $message, '{{' ) !== false ) {
592 - $popts = $this->getParserOptions();
593 - $popts->setInterfaceMessage( $interface );
594 - $message = $this->mParser->transformMsg( $message, $popts );
595 - }
 683+ $popts = $this->getParserOptions();
 684+ $popts->setInterfaceMessage( $interface );
 685+ $message = $this->mParser->transformMsg( $message, $popts );
596686 }
597687 return $message;
598688 }
599689
600690 function disable() { $this->mDisable = true; }
601691 function enable() { $this->mDisable = false; }
602 -
 692+
603693 /** @deprecated */
604694 function disableTransform(){
605695 wfDeprecated( __METHOD__ );
@@ -680,12 +770,14 @@
681771 * Clear all stored messages. Mainly used after a mass rebuild.
682772 */
683773 function clear() {
684 - global $wgLocalMessageCache;
685774 if( $this->mUseCache ) {
686 - # Global cache
687 - $this->mMemc->delete( $this->mMemcKey );
688 - # Invalidate all local caches
689 - $this->mMemc->delete( "{$this->mMemcKey}-hash" );
 775+ $langs = Language::getLanguageNames( false );
 776+ foreach ( array_keys($langs) as $code ) {
 777+ # Global cache
 778+ $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
 779+ # Invalidate all local caches
 780+ $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
 781+ }
690782 }
691783 }
692784
@@ -761,4 +853,15 @@
762854 $this->addMessages( $mergedMessages, $langcode );
763855 }
764856
 857+ public function figureMessage( $key ) {
 858+ global $wgContLanguageCode;
 859+ $pieces = explode('/', $key, 2);
 860+
 861+ $key = $pieces[0];
 862+
 863+ # Language the user is translating to
 864+ $langCode = isset($pieces[1]) ? $pieces[1] : $wgContLanguageCode;
 865+ return array( $key, $langCode );
 866+ }
 867+
765868 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r36800Revert r35821, the change brokes the message cache what the MediaWiki:Convers...shinjiman03:02, 30 June 2008
r37114* Re-implement the message caches for r35821...shinjiman15:39, 5 July 2008
r41928Fixed regression from r35821: the status key is meant to prevent DB overload,...tstarling13:02, 10 October 2008

Status & tagging log