r86905 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86904‎ | r86905 | r86906 >
Date:21:38, 25 April 2011
Author:reedy
Status:ok
Tags:
Comment:
Move 5 more classes into cache/
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/LinkBatch.php (deleted) (history)
  • /trunk/phase3/includes/LinkCache.php (deleted) (history)
  • /trunk/phase3/includes/MemcachedSessions.php (deleted) (history)
  • /trunk/phase3/includes/MessageCache.php (deleted) (history)
  • /trunk/phase3/includes/SquidUpdate.php (deleted) (history)
  • /trunk/phase3/includes/cache/LinkBatch.php (added) (history)
  • /trunk/phase3/includes/cache/LinkCache.php (added) (history)
  • /trunk/phase3/includes/cache/MemcachedSessions.php (added) (history)
  • /trunk/phase3/includes/cache/MessageCache.php (added) (history)
  • /trunk/phase3/includes/cache/SquidUpdate.php (added) (history)

Diff [purge]

Index: trunk/phase3/includes/LinkBatch.php
@@ -1,185 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Class representing a list of titles
6 - * The execute() method checks them all for existence and adds them to a LinkCache object
7 - *
8 - * @ingroup Cache
9 - */
10 -class LinkBatch {
11 - /**
12 - * 2-d array, first index namespace, second index dbkey, value arbitrary
13 - */
14 - var $data = array();
15 -
16 - /**
17 - * For debugging which method is using this class.
18 - */
19 - protected $caller;
20 -
21 - function __construct( $arr = array() ) {
22 - foreach( $arr as $item ) {
23 - $this->addObj( $item );
24 - }
25 - }
26 -
27 - /**
28 - * Use ->setCaller( __METHOD__ ) to indicate which code is using this
29 - * class. Only used in debugging output.
30 - * @since 1.17
31 - */
32 - public function setCaller( $caller ) {
33 - $this->caller = $caller;
34 - }
35 -
36 - /**
37 - * @param $title Title
38 - * @return void
39 - */
40 - public function addObj( $title ) {
41 - if ( is_object( $title ) ) {
42 - $this->add( $title->getNamespace(), $title->getDBkey() );
43 - } else {
44 - wfDebug( "Warning: LinkBatch::addObj got invalid title object\n" );
45 - }
46 - }
47 -
48 - public function add( $ns, $dbkey ) {
49 - if ( $ns < 0 ) {
50 - return;
51 - }
52 - if ( !array_key_exists( $ns, $this->data ) ) {
53 - $this->data[$ns] = array();
54 - }
55 -
56 - $this->data[$ns][str_replace( ' ', '_', $dbkey )] = 1;
57 - }
58 -
59 - /**
60 - * Set the link list to a given 2-d array
61 - * First key is the namespace, second is the DB key, value arbitrary
62 - */
63 - public function setArray( $array ) {
64 - $this->data = $array;
65 - }
66 -
67 - /**
68 - * Returns true if no pages have been added, false otherwise.
69 - */
70 - public function isEmpty() {
71 - return ($this->getSize() == 0);
72 - }
73 -
74 - /**
75 - * Returns the size of the batch.
76 - */
77 - public function getSize() {
78 - return count( $this->data );
79 - }
80 -
81 - /**
82 - * Do the query and add the results to the LinkCache object
83 - * Return an array mapping PDBK to ID
84 - */
85 - public function execute() {
86 - $linkCache = LinkCache::singleton();
87 - return $this->executeInto( $linkCache );
88 - }
89 -
90 - /**
91 - * Do the query and add the results to a given LinkCache object
92 - * Return an array mapping PDBK to ID
93 - */
94 - protected function executeInto( &$cache ) {
95 - wfProfileIn( __METHOD__ );
96 - $res = $this->doQuery();
97 - $ids = $this->addResultToCache( $cache, $res );
98 - $this->doGenderQuery();
99 - wfProfileOut( __METHOD__ );
100 - return $ids;
101 - }
102 -
103 - /**
104 - * Add a ResultWrapper containing IDs and titles to a LinkCache object.
105 - * As normal, titles will go into the static Title cache field.
106 - * This function *also* stores extra fields of the title used for link
107 - * parsing to avoid extra DB queries.
108 - */
109 - public function addResultToCache( $cache, $res ) {
110 - if ( !$res ) {
111 - return array();
112 - }
113 -
114 - // For each returned entry, add it to the list of good links, and remove it from $remaining
115 -
116 - $ids = array();
117 - $remaining = $this->data;
118 - foreach ( $res as $row ) {
119 - $title = Title::makeTitle( $row->page_namespace, $row->page_title );
120 - $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
121 - $ids[$title->getPrefixedDBkey()] = $row->page_id;
122 - unset( $remaining[$row->page_namespace][$row->page_title] );
123 - }
124 -
125 - // The remaining links in $data are bad links, register them as such
126 - foreach ( $remaining as $ns => $dbkeys ) {
127 - foreach ( $dbkeys as $dbkey => $unused ) {
128 - $title = Title::makeTitle( $ns, $dbkey );
129 - $cache->addBadLinkObj( $title );
130 - $ids[$title->getPrefixedDBkey()] = 0;
131 - }
132 - }
133 - return $ids;
134 - }
135 -
136 - /**
137 - * Perform the existence test query, return a ResultWrapper with page_id fields
138 - */
139 - public function doQuery() {
140 - if ( $this->isEmpty() ) {
141 - return false;
142 - }
143 - wfProfileIn( __METHOD__ );
144 -
145 - // This is similar to LinkHolderArray::replaceInternal
146 - $dbr = wfGetDB( DB_SLAVE );
147 - $table = 'page';
148 - $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len',
149 - 'page_is_redirect', 'page_latest' );
150 - $conds = $this->constructSet( 'page', $dbr );
151 -
152 - // Do query
153 - $caller = __METHOD__;
154 - if ( strval( $this->caller ) !== '' ) {
155 - $caller .= " (for {$this->caller})";
156 - }
157 - $res = $dbr->select( $table, $fields, $conds, $caller );
158 - wfProfileOut( __METHOD__ );
159 - return $res;
160 - }
161 -
162 - public function doGenderQuery() {
163 - if ( $this->isEmpty() ) {
164 - return false;
165 - }
166 -
167 - global $wgContLang;
168 - if ( !$wgContLang->needsGenderDistinction() ) {
169 - return false;
170 - }
171 -
172 - $genderCache = GenderCache::singleton();
173 - $genderCache->dolinkBatch( $this->data, $this->caller );
174 - }
175 -
176 - /**
177 - * Construct a WHERE clause which will match all the given titles.
178 - *
179 - * @param $prefix String: the appropriate table's field name prefix ('page', 'pl', etc)
180 - * @param $db DatabaseBase object to use
181 - * @return mixed string with SQL where clause fragment, or false if no items.
182 - */
183 - public function constructSet( $prefix, $db ) {
184 - return $db->makeWhereFrom2d( $this->data, "{$prefix}_namespace", "{$prefix}_title" );
185 - }
186 -}
Index: trunk/phase3/includes/MessageCache.php
@@ -1,939 +0,0 @@
2 -<?php
3 -/**
4 - * @file
5 - * @ingroup Cache
6 - */
7 -
8 -/**
9 - *
10 - */
11 -define( 'MSG_LOAD_TIMEOUT', 60 );
12 -define( 'MSG_LOCK_TIMEOUT', 10 );
13 -define( 'MSG_WAIT_TIMEOUT', 10 );
14 -define( 'MSG_CACHE_VERSION', 1 );
15 -
16 -/**
17 - * Message cache
18 - * Performs various MediaWiki namespace-related functions
19 - * @ingroup Cache
20 - */
21 -class MessageCache {
22 - /**
23 - * Process local cache of loaded messages that are defined in
24 - * MediaWiki namespace. First array level is a language code,
25 - * second level is message key and the values are either message
26 - * content prefixed with space, or !NONEXISTENT for negative
27 - * caching.
28 - */
29 - protected $mCache;
30 -
31 - // Should mean that database cannot be used, but check
32 - protected $mDisable;
33 -
34 - /// Lifetime for cache, used by object caching
35 - protected $mExpiry;
36 -
37 - /**
38 - * Message cache has it's own parser which it uses to transform
39 - * messages.
40 - */
41 - protected $mParserOptions, $mParser;
42 -
43 - /// Variable for tracking which variables are already loaded
44 - protected $mLoadedLanguages = array();
45 -
46 - /**
47 - * Used for automatic detection of most used messages.
48 - */
49 - protected $mRequestedMessages = array();
50 -
51 - /**
52 - * How long the message request counts are stored. Longer period gives
53 - * better sample, but also takes longer to adapt changes. The counts
54 - * are aggregrated per day, regardless of the value of this variable.
55 - */
56 - protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
57 -
58 - /**
59 - * Filter the tail of less used messages that are requested more seldom
60 - * than this factor times the number of request of most requested message.
61 - * These messages are not loaded in the default set, but are still cached
62 - * individually on demand with the normal cache expiry time.
63 - */
64 - protected static $mAdaptiveInclusionThreshold = 0.05;
65 -
66 - /**
67 - * Singleton instance
68 - */
69 - private static $instance;
70 -
71 - /**
72 - * @var bool
73 - */
74 - protected $mInParser = false;
75 -
76 - /**
77 - * Get the signleton instance of this class
78 - *
79 - * @since 1.18
80 - * @return MessageCache object
81 - */
82 - public static function singleton() {
83 - if ( is_null( self::$instance ) ) {
84 - global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
85 - self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
86 - }
87 - return self::$instance;
88 - }
89 -
90 - /**
91 - * Destroy the singleton instance
92 - *
93 - * @since 1.18
94 - */
95 - public static function destroyInstance() {
96 - self::$instance = null;
97 - }
98 -
99 - function __construct( $memCached, $useDB, $expiry ) {
100 - if ( !$memCached ) {
101 - $memCached = wfGetCache( CACHE_NONE );
102 - }
103 -
104 - $this->mMemc = $memCached;
105 - $this->mDisable = !$useDB;
106 - $this->mExpiry = $expiry;
107 - }
108 -
109 - /**
110 - * ParserOptions is lazy initialised.
111 - *
112 - * @return ParserOptions
113 - */
114 - function getParserOptions() {
115 - if ( !$this->mParserOptions ) {
116 - $this->mParserOptions = new ParserOptions;
117 - }
118 - return $this->mParserOptions;
119 - }
120 -
121 - /**
122 - * Try to load the cache from a local file.
123 - * Actual format of the file depends on the $wgLocalMessageCacheSerialized
124 - * setting.
125 - *
126 - * @param $hash String: the hash of contents, to check validity.
127 - * @param $code Mixed: Optional language code, see documenation of load().
128 - * @return false on failure.
129 - */
130 - function loadFromLocal( $hash, $code ) {
131 - global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
132 -
133 - $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
134 -
135 - # Check file existence
136 - wfSuppressWarnings();
137 - $file = fopen( $filename, 'r' );
138 - wfRestoreWarnings();
139 - if ( !$file ) {
140 - return false; // No cache file
141 - }
142 -
143 - if ( $wgLocalMessageCacheSerialized ) {
144 - // Check to see if the file has the hash specified
145 - $localHash = fread( $file, 32 );
146 - if ( $hash === $localHash ) {
147 - // All good, get the rest of it
148 - $serialized = '';
149 - while ( !feof( $file ) ) {
150 - $serialized .= fread( $file, 100000 );
151 - }
152 - fclose( $file );
153 - return $this->setCache( unserialize( $serialized ), $code );
154 - } else {
155 - fclose( $file );
156 - return false; // Wrong hash
157 - }
158 - } else {
159 - $localHash = substr( fread( $file, 40 ), 8 );
160 - fclose( $file );
161 - if ( $hash != $localHash ) {
162 - return false; // Wrong hash
163 - }
164 -
165 - # Require overwrites the member variable or just shadows it?
166 - require( $filename );
167 - return $this->setCache( $this->mCache, $code );
168 - }
169 - }
170 -
171 - /**
172 - * Save the cache to a local file.
173 - */
174 - function saveToLocal( $serialized, $hash, $code ) {
175 - global $wgCacheDirectory;
176 -
177 - $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
178 - wfMkdirParents( $wgCacheDirectory ); // might fail
179 -
180 - wfSuppressWarnings();
181 - $file = fopen( $filename, 'w' );
182 - wfRestoreWarnings();
183 -
184 - if ( !$file ) {
185 - wfDebug( "Unable to open local cache file for writing\n" );
186 - return;
187 - }
188 -
189 - fwrite( $file, $hash . $serialized );
190 - fclose( $file );
191 - @chmod( $filename, 0666 );
192 - }
193 -
194 - function saveToScript( $array, $hash, $code ) {
195 - global $wgCacheDirectory;
196 -
197 - $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
198 - $tempFilename = $filename . '.tmp';
199 - wfMkdirParents( $wgCacheDirectory ); // might fail
200 -
201 - wfSuppressWarnings();
202 - $file = fopen( $tempFilename, 'w' );
203 - wfRestoreWarnings();
204 -
205 - if ( !$file ) {
206 - wfDebug( "Unable to open local cache file for writing\n" );
207 - return;
208 - }
209 -
210 - fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
211 -
212 - foreach ( $array as $key => $message ) {
213 - $key = $this->escapeForScript( $key );
214 - $message = $this->escapeForScript( $message );
215 - fwrite( $file, "'$key' => '$message',\n" );
216 - }
217 -
218 - fwrite( $file, ");\n?>" );
219 - fclose( $file);
220 - rename( $tempFilename, $filename );
221 - }
222 -
223 - function escapeForScript( $string ) {
224 - $string = str_replace( '\\', '\\\\', $string );
225 - $string = str_replace( '\'', '\\\'', $string );
226 - return $string;
227 - }
228 -
229 - /**
230 - * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
231 - *
232 - * @return bool
233 - */
234 - function setCache( $cache, $code ) {
235 - if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
236 - $this->mCache[$code] = $cache;
237 - return true;
238 - } else {
239 - return false;
240 - }
241 - }
242 -
243 - /**
244 - * Loads messages from caches or from database in this order:
245 - * (1) local message cache (if $wgUseLocalMessageCache is enabled)
246 - * (2) memcached
247 - * (3) from the database.
248 - *
249 - * When succesfully loading from (2) or (3), all higher level caches are
250 - * updated for the newest version.
251 - *
252 - * Nothing is loaded if member variable mDisable is true, either manually
253 - * set by calling code or if message loading fails (is this possible?).
254 - *
255 - * Returns true if cache is already populated or it was succesfully populated,
256 - * or false if populating empty cache fails. Also returns true if MessageCache
257 - * is disabled.
258 - *
259 - * @param $code String: language to which load messages
260 - */
261 - function load( $code = false ) {
262 - global $wgUseLocalMessageCache;
263 -
264 - if( !is_string( $code ) ) {
265 - # This isn't really nice, so at least make a note about it and try to
266 - # fall back
267 - wfDebug( __METHOD__ . " called without providing a language code\n" );
268 - $code = 'en';
269 - }
270 -
271 - # Don't do double loading...
272 - if ( isset( $this->mLoadedLanguages[$code] ) ) {
273 - return true;
274 - }
275 -
276 - # 8 lines of code just to say (once) that message cache is disabled
277 - if ( $this->mDisable ) {
278 - static $shownDisabled = false;
279 - if ( !$shownDisabled ) {
280 - wfDebug( __METHOD__ . ": disabled\n" );
281 - $shownDisabled = true;
282 - }
283 - return true;
284 - }
285 -
286 - # Loading code starts
287 - wfProfileIn( __METHOD__ );
288 - $success = false; # Keep track of success
289 - $where = array(); # Debug info, delayed to avoid spamming debug log too much
290 - $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
291 -
292 - # (1) local cache
293 - # Hash of the contents is stored in memcache, to detect if local cache goes
294 - # out of date (due to update in other thread?)
295 - if ( $wgUseLocalMessageCache ) {
296 - wfProfileIn( __METHOD__ . '-fromlocal' );
297 -
298 - $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
299 - if ( $hash ) {
300 - $success = $this->loadFromLocal( $hash, $code );
301 - if ( $success ) $where[] = 'got from local cache';
302 - }
303 - wfProfileOut( __METHOD__ . '-fromlocal' );
304 - }
305 -
306 - # (2) memcache
307 - # Fails if nothing in cache, or in the wrong version.
308 - if ( !$success ) {
309 - wfProfileIn( __METHOD__ . '-fromcache' );
310 - $cache = $this->mMemc->get( $cacheKey );
311 - $success = $this->setCache( $cache, $code );
312 - if ( $success ) {
313 - $where[] = 'got from global cache';
314 - $this->saveToCaches( $cache, false, $code );
315 - }
316 - wfProfileOut( __METHOD__ . '-fromcache' );
317 - }
318 -
319 - # (3)
320 - # Nothing in caches... so we need create one and store it in caches
321 - if ( !$success ) {
322 - $where[] = 'cache is empty';
323 - $where[] = 'loading from database';
324 -
325 - $this->lock( $cacheKey );
326 -
327 - # Limit the concurrency of loadFromDB to a single process
328 - # This prevents the site from going down when the cache expires
329 - $statusKey = wfMemcKey( 'messages', $code, 'status' );
330 - $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
331 - if ( $success ) {
332 - $cache = $this->loadFromDB( $code );
333 - $success = $this->setCache( $cache, $code );
334 - }
335 - if ( $success ) {
336 - $success = $this->saveToCaches( $cache, true, $code );
337 - if ( $success ) {
338 - $this->mMemc->delete( $statusKey );
339 - } else {
340 - $this->mMemc->set( $statusKey, 'error', 60 * 5 );
341 - wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
342 - }
343 - }
344 - $this->unlock($cacheKey);
345 - }
346 -
347 - if ( !$success ) {
348 - # Bad luck... this should not happen
349 - $where[] = 'loading FAILED - cache is disabled';
350 - $info = implode( ', ', $where );
351 - wfDebug( __METHOD__ . ": Loading $code... $info\n" );
352 - $this->mDisable = true;
353 - $this->mCache = false;
354 - } else {
355 - # All good, just record the success
356 - $info = implode( ', ', $where );
357 - wfDebug( __METHOD__ . ": Loading $code... $info\n" );
358 - $this->mLoadedLanguages[$code] = true;
359 - }
360 - wfProfileOut( __METHOD__ );
361 - return $success;
362 - }
363 -
364 - /**
365 - * Loads cacheable messages from the database. Messages bigger than
366 - * $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
367 - * on-demand from the database later.
368 - *
369 - * @param $code String: language code.
370 - * @return Array: loaded messages for storing in caches.
371 - */
372 - function loadFromDB( $code ) {
373 - wfProfileIn( __METHOD__ );
374 - global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
375 - $dbr = wfGetDB( DB_SLAVE );
376 - $cache = array();
377 -
378 - # Common conditions
379 - $conds = array(
380 - 'page_is_redirect' => 0,
381 - 'page_namespace' => NS_MEDIAWIKI,
382 - );
383 -
384 - $mostused = array();
385 - if ( $wgAdaptiveMessageCache ) {
386 - $mostused = $this->getMostUsedMessages();
387 - if ( $code !== $wgLanguageCode ) {
388 - foreach ( $mostused as $key => $value ) {
389 - $mostused[$key] = "$value/$code";
390 - }
391 - }
392 - }
393 -
394 - if ( count( $mostused ) ) {
395 - $conds['page_title'] = $mostused;
396 - } elseif ( $code !== $wgLanguageCode ) {
397 - $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
398 - } else {
399 - # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
400 - # other than language code.
401 - $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
402 - }
403 -
404 - # Conditions to fetch oversized pages to ignore them
405 - $bigConds = $conds;
406 - $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
407 -
408 - # Load titles for all oversized pages in the MediaWiki namespace
409 - $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
410 - foreach ( $res as $row ) {
411 - $cache[$row->page_title] = '!TOO BIG';
412 - }
413 -
414 - # Conditions to load the remaining pages with their contents
415 - $smallConds = $conds;
416 - $smallConds[] = 'page_latest=rev_id';
417 - $smallConds[] = 'rev_text_id=old_id';
418 - $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
419 -
420 - $res = $dbr->select(
421 - array( 'page', 'revision', 'text' ),
422 - array( 'page_title', 'old_text', 'old_flags' ),
423 - $smallConds,
424 - __METHOD__ . "($code)-small"
425 - );
426 -
427 - foreach ( $res as $row ) {
428 - $cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
429 - }
430 -
431 - foreach ( $mostused as $key ) {
432 - if ( !isset( $cache[$key] ) ) {
433 - $cache[$key] = '!NONEXISTENT';
434 - }
435 - }
436 -
437 - $cache['VERSION'] = MSG_CACHE_VERSION;
438 - wfProfileOut( __METHOD__ );
439 - return $cache;
440 - }
441 -
442 - /**
443 - * Updates cache as necessary when message page is changed
444 - *
445 - * @param $title String: name of the page changed.
446 - * @param $text Mixed: new contents of the page.
447 - */
448 - public function replace( $title, $text ) {
449 - global $wgMaxMsgCacheEntrySize;
450 - wfProfileIn( __METHOD__ );
451 -
452 - if ( $this->mDisable ) {
453 - wfProfileOut( __METHOD__ );
454 - return;
455 - }
456 -
457 - list( $msg, $code ) = $this->figureMessage( $title );
458 -
459 - $cacheKey = wfMemcKey( 'messages', $code );
460 - $this->load( $code );
461 - $this->lock( $cacheKey );
462 -
463 - $titleKey = wfMemcKey( 'messages', 'individual', $title );
464 -
465 - if ( $text === false ) {
466 - # Article was deleted
467 - $this->mCache[$code][$title] = '!NONEXISTENT';
468 - $this->mMemc->delete( $titleKey );
469 - } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
470 - # Check for size
471 - $this->mCache[$code][$title] = '!TOO BIG';
472 - $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
473 - } else {
474 - $this->mCache[$code][$title] = ' ' . $text;
475 - $this->mMemc->delete( $titleKey );
476 - }
477 -
478 - # Update caches
479 - $this->saveToCaches( $this->mCache[$code], true, $code );
480 - $this->unlock( $cacheKey );
481 -
482 - // Also delete cached sidebar... just in case it is affected
483 - $codes = array( $code );
484 - if ( $code === 'en' ) {
485 - // Delete all sidebars, like for example on action=purge on the
486 - // sidebar messages
487 - $codes = array_keys( Language::getLanguageNames() );
488 - }
489 -
490 - global $parserMemc;
491 - foreach ( $codes as $code ) {
492 - $sidebarKey = wfMemcKey( 'sidebar', $code );
493 - $parserMemc->delete( $sidebarKey );
494 - }
495 -
496 - // Update the message in the message blob store
497 - global $wgContLang;
498 - MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
499 -
500 - wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
501 -
502 - wfProfileOut( __METHOD__ );
503 - }
504 -
505 - /**
506 - * Shortcut to update caches.
507 - *
508 - * @param $cache Array: cached messages with a version.
509 - * @param $memc Bool: Wether to update or not memcache.
510 - * @param $code String: Language code.
511 - * @return False on somekind of error.
512 - */
513 - protected function saveToCaches( $cache, $memc = true, $code = false ) {
514 - wfProfileIn( __METHOD__ );
515 - global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
516 -
517 - $cacheKey = wfMemcKey( 'messages', $code );
518 -
519 - if ( $memc ) {
520 - $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
521 - } else {
522 - $success = true;
523 - }
524 -
525 - # Save to local cache
526 - if ( $wgUseLocalMessageCache ) {
527 - $serialized = serialize( $cache );
528 - $hash = md5( $serialized );
529 - $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
530 - if ($wgLocalMessageCacheSerialized) {
531 - $this->saveToLocal( $serialized, $hash, $code );
532 - } else {
533 - $this->saveToScript( $cache, $hash, $code );
534 - }
535 - }
536 -
537 - wfProfileOut( __METHOD__ );
538 - return $success;
539 - }
540 -
541 - /**
542 - * Represents a write lock on the messages key
543 - *
544 - * @return Boolean: success
545 - */
546 - function lock( $key ) {
547 - $lockKey = $key . ':lock';
548 - for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
549 - sleep( 1 );
550 - }
551 -
552 - return $i >= MSG_WAIT_TIMEOUT;
553 - }
554 -
555 - function unlock( $key ) {
556 - $lockKey = $key . ':lock';
557 - $this->mMemc->delete( $lockKey );
558 - }
559 -
560 - /**
561 - * Get a message from either the content language or the user language.
562 - *
563 - * @param $key String: the message cache key
564 - * @param $useDB Boolean: get the message from the DB, false to use only
565 - * the localisation
566 - * @param $langcode String: code of the language to get the message for, if
567 - * it is a valid code create a language for that language,
568 - * if it is a string but not a valid code then make a basic
569 - * language object, if it is a false boolean then use the
570 - * current users language (as a fallback for the old
571 - * parameter functionality), or if it is a true boolean
572 - * then use the wikis content language (also as a
573 - * fallback).
574 - * @param $isFullKey Boolean: specifies whether $key is a two part key
575 - * "msg/lang".
576 - */
577 - function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
578 - global $wgLanguageCode, $wgContLang;
579 -
580 - if ( !is_string( $key ) ) {
581 - throw new MWException( 'Non-string key given' );
582 - }
583 -
584 - if ( strval( $key ) === '' ) {
585 - # Shortcut: the empty key is always missing
586 - return false;
587 - }
588 -
589 - $lang = wfGetLangObj( $langcode );
590 - if ( !$lang ) {
591 - throw new MWException( "Bad lang code $langcode given" );
592 - }
593 -
594 - $langcode = $lang->getCode();
595 -
596 - $message = false;
597 -
598 - # Normalise title-case input (with some inlining)
599 - $lckey = str_replace( ' ', '_', $key );
600 - if ( ord( $key ) < 128 ) {
601 - $lckey[0] = strtolower( $lckey[0] );
602 - $uckey = ucfirst( $lckey );
603 - } else {
604 - $lckey = $wgContLang->lcfirst( $lckey );
605 - $uckey = $wgContLang->ucfirst( $lckey );
606 - }
607 -
608 - /**
609 - * Record each message request, but only once per request.
610 - * This information is not used unless $wgAdaptiveMessageCache
611 - * is enabled.
612 - */
613 - $this->mRequestedMessages[$uckey] = true;
614 -
615 - # Try the MediaWiki namespace
616 - if( !$this->mDisable && $useDB ) {
617 - $title = $uckey;
618 - if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
619 - $title .= '/' . $langcode;
620 - }
621 - $message = $this->getMsgFromNamespace( $title, $langcode );
622 - }
623 -
624 - # Try the array in the language object
625 - if ( $message === false ) {
626 - $message = $lang->getMessage( $lckey );
627 - if ( is_null( $message ) ) {
628 - $message = false;
629 - }
630 - }
631 -
632 - # Try the array of another language
633 - if( $message === false ) {
634 - $parts = explode( '/', $lckey );
635 - # We may get calls for things that are http-urls from sidebar
636 - # Let's not load nonexistent languages for those
637 - # They usually have more than one slash.
638 - if ( count( $parts ) == 2 && $parts[1] !== '' ) {
639 - $message = Language::getMessageFor( $parts[0], $parts[1] );
640 - if ( is_null( $message ) ) {
641 - $message = false;
642 - }
643 - }
644 - }
645 -
646 - # Is this a custom message? Try the default language in the db...
647 - if( ( $message === false || $message === '-' ) &&
648 - !$this->mDisable && $useDB &&
649 - !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
650 - $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
651 - }
652 -
653 - # Final fallback
654 - if( $message === false ) {
655 - return false;
656 - }
657 -
658 - # Fix whitespace
659 - $message = strtr( $message,
660 - array(
661 - # Fix for trailing whitespace, removed by textarea
662 - '&#32;' => ' ',
663 - # Fix for NBSP, converted to space by firefox
664 - '&nbsp;' => "\xc2\xa0",
665 - '&#160;' => "\xc2\xa0",
666 - ) );
667 -
668 - return $message;
669 - }
670 -
671 - /**
672 - * Get a message from the MediaWiki namespace, with caching. The key must
673 - * first be converted to two-part lang/msg form if necessary.
674 - *
675 - * @param $title String: Message cache key with initial uppercase letter.
676 - * @param $code String: code denoting the language to try.
677 - */
678 - function getMsgFromNamespace( $title, $code ) {
679 - global $wgAdaptiveMessageCache;
680 -
681 - $this->load( $code );
682 - if ( isset( $this->mCache[$code][$title] ) ) {
683 - $entry = $this->mCache[$code][$title];
684 - if ( substr( $entry, 0, 1 ) === ' ' ) {
685 - return substr( $entry, 1 );
686 - } elseif ( $entry === '!NONEXISTENT' ) {
687 - return false;
688 - } elseif( $entry === '!TOO BIG' ) {
689 - // Fall through and try invididual message cache below
690 - }
691 - } else {
692 - // XXX: This is not cached in process cache, should it?
693 - $message = false;
694 - wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
695 - if ( $message !== false ) {
696 - return $message;
697 - }
698 -
699 - /**
700 - * If message cache is in normal mode, it is guaranteed
701 - * (except bugs) that there is always entry (or placeholder)
702 - * in the cache if message exists. Thus we can do minor
703 - * performance improvement and return false early.
704 - */
705 - if ( !$wgAdaptiveMessageCache ) {
706 - return false;
707 - }
708 - }
709 -
710 - # Try the individual message cache
711 - $titleKey = wfMemcKey( 'messages', 'individual', $title );
712 - $entry = $this->mMemc->get( $titleKey );
713 - if ( $entry ) {
714 - if ( substr( $entry, 0, 1 ) === ' ' ) {
715 - $this->mCache[$code][$title] = $entry;
716 - return substr( $entry, 1 );
717 - } elseif ( $entry === '!NONEXISTENT' ) {
718 - $this->mCache[$code][$title] = '!NONEXISTENT';
719 - return false;
720 - } else {
721 - # Corrupt/obsolete entry, delete it
722 - $this->mMemc->delete( $titleKey );
723 - }
724 - }
725 -
726 - # Try loading it from the database
727 - $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
728 - if ( $revision ) {
729 - $message = $revision->getText();
730 - $this->mCache[$code][$title] = ' ' . $message;
731 - $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
732 - } else {
733 - $message = false;
734 - $this->mCache[$code][$title] = '!NONEXISTENT';
735 - $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
736 - }
737 -
738 - return $message;
739 - }
740 -
741 - /**
742 - * @param $message string
743 - * @param $interface bool
744 - * @param $language
745 - * @param $title Title
746 - * @return string
747 - */
748 - function transform( $message, $interface = false, $language = null, $title = null ) {
749 - // Avoid creating parser if nothing to transform
750 - if( strpos( $message, '{{' ) === false ) {
751 - return $message;
752 - }
753 -
754 - if ( $this->mInParser ) {
755 - return $message;
756 - }
757 -
758 - $parser = $this->getParser();
759 - if ( $parser ) {
760 - $popts = $this->getParserOptions();
761 - $popts->setInterfaceMessage( $interface );
762 - $popts->setTargetLanguage( $language );
763 - $popts->setUserLang( $language );
764 -
765 - $this->mInParser = true;
766 - $message = $parser->transformMsg( $message, $popts, $title );
767 - $this->mInParser = false;
768 - }
769 - return $message;
770 - }
771 -
772 - /**
773 - * @return Parser
774 - */
775 - function getParser() {
776 - global $wgParser, $wgParserConf;
777 - if ( !$this->mParser && isset( $wgParser ) ) {
778 - # Do some initialisation so that we don't have to do it twice
779 - $wgParser->firstCallInit();
780 - # Clone it and store it
781 - $class = $wgParserConf['class'];
782 - if ( $class == 'Parser_DiffTest' ) {
783 - # Uncloneable
784 - $this->mParser = new $class( $wgParserConf );
785 - } else {
786 - $this->mParser = clone $wgParser;
787 - }
788 - }
789 - return $this->mParser;
790 - }
791 -
792 - /**
793 - * @param $text string
794 - * @param $string Title|string
795 - * @param $title Title
796 - * @param $interface bool
797 - * @param $linestart bool
798 - * @param $language
799 - * @return ParserOutput
800 - */
801 - public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null ) {
802 - if ( $this->mInParser ) {
803 - return htmlspecialchars( $text );
804 - }
805 -
806 - $parser = $this->getParser();
807 - $popts = $this->getParserOptions();
808 -
809 - if ( $interface ) {
810 - $popts->setInterfaceMessage( true );
811 - }
812 - if ( $language !== null ) {
813 - $popts->setTargetLanguage( $language );
814 - }
815 -
816 - if ( !$title || !$title instanceof Title ) {
817 - global $wgTitle;
818 - $title = $wgTitle;
819 - }
820 -
821 - $this->mInParser = true;
822 - $res = $parser->parse( $text, $title, $popts, $linestart );
823 - $this->mInParser = false;
824 -
825 - return $res;
826 - }
827 -
828 - function disable() {
829 - $this->mDisable = true;
830 - }
831 -
832 - function enable() {
833 - $this->mDisable = false;
834 - }
835 -
836 - /**
837 - * Clear all stored messages. Mainly used after a mass rebuild.
838 - */
839 - function clear() {
840 - $langs = Language::getLanguageNames( false );
841 - foreach ( array_keys($langs) as $code ) {
842 - # Global cache
843 - $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
844 - # Invalidate all local caches
845 - $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
846 - }
847 - $this->mLoadedLanguages = array();
848 - }
849 -
850 - public function figureMessage( $key ) {
851 - global $wgLanguageCode;
852 - $pieces = explode( '/', $key );
853 - if( count( $pieces ) < 2 ) {
854 - return array( $key, $wgLanguageCode );
855 - }
856 -
857 - $lang = array_pop( $pieces );
858 - $validCodes = Language::getLanguageNames();
859 - if( !array_key_exists( $lang, $validCodes ) ) {
860 - return array( $key, $wgLanguageCode );
861 - }
862 -
863 - $message = implode( '/', $pieces );
864 - return array( $message, $lang );
865 - }
866 -
867 - public static function logMessages() {
868 - wfProfileIn( __METHOD__ );
869 - global $wgAdaptiveMessageCache;
870 - if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
871 - wfProfileOut( __METHOD__ );
872 - return;
873 - }
874 -
875 - $cachekey = wfMemckey( 'message-profiling' );
876 - $cache = wfGetCache( CACHE_DB );
877 - $data = $cache->get( $cachekey );
878 -
879 - if ( !$data ) {
880 - $data = array();
881 - }
882 -
883 - $age = self::$mAdaptiveDataAge;
884 - $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
885 - foreach ( array_keys( $data ) as $key ) {
886 - if ( $key < $filterDate ) {
887 - unset( $data[$key] );
888 - }
889 - }
890 -
891 - $index = substr( wfTimestampNow(), 0, 8 );
892 - if ( !isset( $data[$index] ) ) {
893 - $data[$index] = array();
894 - }
895 -
896 - foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
897 - if ( !isset( $data[$index][$message] ) ) {
898 - $data[$index][$message] = 0;
899 - }
900 - $data[$index][$message]++;
901 - }
902 -
903 - $cache->set( $cachekey, $data );
904 - wfProfileOut( __METHOD__ );
905 - }
906 -
907 - public function getMostUsedMessages() {
908 - wfProfileIn( __METHOD__ );
909 - $cachekey = wfMemcKey( 'message-profiling' );
910 - $cache = wfGetCache( CACHE_DB );
911 - $data = $cache->get( $cachekey );
912 - if ( !$data ) {
913 - wfProfileOut( __METHOD__ );
914 - return array();
915 - }
916 -
917 - $list = array();
918 -
919 - foreach( $data as $messages ) {
920 - foreach( $messages as $message => $count ) {
921 - $key = $message;
922 - if ( !isset( $list[$key] ) ) {
923 - $list[$key] = 0;
924 - }
925 - $list[$key] += $count;
926 - }
927 - }
928 -
929 - $max = max( $list );
930 - foreach ( $list as $message => $count ) {
931 - if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
932 - unset( $list[$message] );
933 - }
934 - }
935 -
936 - wfProfileOut( __METHOD__ );
937 - return array_keys( $list );
938 - }
939 -
940 -}
Index: trunk/phase3/includes/MemcachedSessions.php
@@ -1,98 +0,0 @@
2 -<?php
3 -/**
4 - * This file gets included if $wgSessionsInMemcache is set in the config.
5 - * It redirects session handling functions to store their data in memcached
6 - * instead of the local filesystem. Depending on circumstances, it may also
7 - * be necessary to change the cookie settings to work across hostnames.
8 - * See: http://www.php.net/manual/en/function.session-set-save-handler.php
9 - *
10 - * @file
11 - * @ingroup Cache
12 - */
13 -
14 -/**
15 - * Get a cache key for the given session id.
16 - *
17 - * @param $id String: session id
18 - * @return String: cache key
19 - */
20 -function memsess_key( $id ) {
21 - return wfMemcKey( 'session', $id );
22 -}
23 -
24 -/**
25 - * Callback when opening a session.
26 - * NOP: $wgMemc should be set up already.
27 - *
28 - * @param $save_path String: path used to store session files, unused
29 - * @param $session_name String: session name
30 - * @return Boolean: success
31 - */
32 -function memsess_open( $save_path, $session_name ) {
33 - return true;
34 -}
35 -
36 -/**
37 - * Callback when closing a session.
38 - * NOP.
39 - *
40 - * @return Boolean: success
41 - */
42 -function memsess_close() {
43 - return true;
44 -}
45 -
46 -/**
47 - * Callback when reading session data.
48 - *
49 - * @param $id String: session id
50 - * @return Mixed: session data
51 - */
52 -function memsess_read( $id ) {
53 - global $wgMemc;
54 - $data = $wgMemc->get( memsess_key( $id ) );
55 - if( ! $data ) return '';
56 - return $data;
57 -}
58 -
59 -/**
60 - * Callback when writing session data.
61 - *
62 - * @param $id String: session id
63 - * @param $data Mixed: session data
64 - * @return Boolean: success
65 - */
66 -function memsess_write( $id, $data ) {
67 - global $wgMemc;
68 - $wgMemc->set( memsess_key( $id ), $data, 3600 );
69 - return true;
70 -}
71 -
72 -/**
73 - * Callback to destroy a session when calling session_destroy().
74 - *
75 - * @param $id String: session id
76 - * @return Boolean: success
77 - */
78 -function memsess_destroy( $id ) {
79 - global $wgMemc;
80 -
81 - $wgMemc->delete( memsess_key( $id ) );
82 - return true;
83 -}
84 -
85 -/**
86 - * Callback to execute garbage collection.
87 - * NOP: Memcached performs garbage collection.
88 - *
89 - * @param $maxlifetime Integer: maximum session life time
90 - * @return Boolean: success
91 - */
92 -function memsess_gc( $maxlifetime ) {
93 - return true;
94 -}
95 -
96 -function memsess_write_close() {
97 - session_write_close();
98 -}
99 -
Index: trunk/phase3/includes/SquidUpdate.php
@@ -1,203 +0,0 @@
2 -<?php
3 -/**
4 - * See deferred.txt
5 - * @file
6 - * @ingroup Cache
7 - */
8 -
9 -/**
10 - * Handles purging appropriate Squid URLs given a title (or titles)
11 - * @ingroup Cache
12 - */
13 -class SquidUpdate {
14 - var $urlArr, $mMaxTitles;
15 -
16 - function __construct( $urlArr = Array(), $maxTitles = false ) {
17 - global $wgMaxSquidPurgeTitles;
18 - if ( $maxTitles === false ) {
19 - $this->mMaxTitles = $wgMaxSquidPurgeTitles;
20 - } else {
21 - $this->mMaxTitles = $maxTitles;
22 - }
23 - if ( count( $urlArr ) > $this->mMaxTitles ) {
24 - $urlArr = array_slice( $urlArr, 0, $this->mMaxTitles );
25 - }
26 - $this->urlArr = $urlArr;
27 - }
28 -
29 - static function newFromLinksTo( &$title ) {
30 - global $wgMaxSquidPurgeTitles;
31 - wfProfileIn( __METHOD__ );
32 -
33 - # Get a list of URLs linking to this page
34 - $dbr = wfGetDB( DB_SLAVE );
35 - $res = $dbr->select( array( 'links', 'page' ),
36 - array( 'page_namespace', 'page_title' ),
37 - array(
38 - 'pl_namespace' => $title->getNamespace(),
39 - 'pl_title' => $title->getDBkey(),
40 - 'pl_from=page_id' ),
41 - __METHOD__ );
42 - $blurlArr = $title->getSquidURLs();
43 - if ( $dbr->numRows( $res ) <= $wgMaxSquidPurgeTitles ) {
44 - foreach ( $res as $BL ) {
45 - $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title ) ;
46 - $blurlArr[] = $tobj->getInternalURL();
47 - }
48 - }
49 -
50 - wfProfileOut( __METHOD__ );
51 - return new SquidUpdate( $blurlArr );
52 - }
53 -
54 - /**
55 - * Create a SquidUpdate from an array of Title objects, or a TitleArray object
56 - */
57 - static function newFromTitles( $titles, $urlArr = array() ) {
58 - global $wgMaxSquidPurgeTitles;
59 - $i = 0;
60 - foreach ( $titles as $title ) {
61 - $urlArr[] = $title->getInternalURL();
62 - if ( $i++ > $wgMaxSquidPurgeTitles ) {
63 - break;
64 - }
65 - }
66 - return new SquidUpdate( $urlArr );
67 - }
68 -
69 - static function newSimplePurge( &$title ) {
70 - $urlArr = $title->getSquidURLs();
71 - return new SquidUpdate( $urlArr );
72 - }
73 -
74 - function doUpdate() {
75 - SquidUpdate::purge( $this->urlArr );
76 - }
77 -
78 - /* Purges a list of Squids defined in $wgSquidServers.
79 - $urlArr should contain the full URLs to purge as values
80 - (example: $urlArr[] = 'http://my.host/something')
81 - XXX report broken Squids per mail or log */
82 -
83 - static function purge( $urlArr ) {
84 - global $wgSquidServers, $wgHTCPMulticastAddress, $wgHTCPPort;
85 -
86 - /*if ( (@$wgSquidServers[0]) == 'echo' ) {
87 - echo implode("<br />\n", $urlArr) . "<br />\n";
88 - return;
89 - }*/
90 -
91 - if( !$urlArr ) {
92 - return;
93 - }
94 -
95 - if ( $wgHTCPMulticastAddress && $wgHTCPPort ) {
96 - return SquidUpdate::HTCPPurge( $urlArr );
97 - }
98 -
99 - wfProfileIn( __METHOD__ );
100 -
101 - $maxSocketsPerSquid = 8; // socket cap per Squid
102 - $urlsPerSocket = 400; // 400 seems to be a good tradeoff, opening a socket takes a while
103 - $socketsPerSquid = ceil( count( $urlArr ) / $urlsPerSocket );
104 - if ( $socketsPerSquid > $maxSocketsPerSquid ) {
105 - $socketsPerSquid = $maxSocketsPerSquid;
106 - }
107 -
108 - $pool = new SquidPurgeClientPool;
109 - $chunks = array_chunk( $urlArr, ceil( count( $urlArr ) / $socketsPerSquid ) );
110 - foreach ( $wgSquidServers as $server ) {
111 - foreach ( $chunks as $chunk ) {
112 - $client = new SquidPurgeClient( $server );
113 - foreach ( $chunk as $url ) {
114 - $client->queuePurge( $url );
115 - }
116 - $pool->addClient( $client );
117 - }
118 - }
119 - $pool->run();
120 -
121 - wfProfileOut( __METHOD__ );
122 - }
123 -
124 - static function HTCPPurge( $urlArr ) {
125 - global $wgHTCPMulticastAddress, $wgHTCPMulticastTTL, $wgHTCPPort;
126 - wfProfileIn( __METHOD__ );
127 -
128 - $htcpOpCLR = 4; // HTCP CLR
129 -
130 - // FIXME PHP doesn't support these socket constants (include/linux/in.h)
131 - if( !defined( "IPPROTO_IP" ) ) {
132 - define( "IPPROTO_IP", 0 );
133 - define( "IP_MULTICAST_LOOP", 34 );
134 - define( "IP_MULTICAST_TTL", 33 );
135 - }
136 -
137 - // pfsockopen doesn't work because we need set_sock_opt
138 - $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
139 - if ( $conn ) {
140 - // Set socket options
141 - socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
142 - if ( $wgHTCPMulticastTTL != 1 )
143 - socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
144 - $wgHTCPMulticastTTL );
145 -
146 - foreach ( $urlArr as $url ) {
147 - if( !is_string( $url ) ) {
148 - throw new MWException( 'Bad purge URL' );
149 - }
150 - $url = SquidUpdate::expand( $url );
151 -
152 - // Construct a minimal HTCP request diagram
153 - // as per RFC 2756
154 - // Opcode 'CLR', no response desired, no auth
155 - $htcpTransID = rand();
156 -
157 - $htcpSpecifier = pack( 'na4na*na8n',
158 - 4, 'HEAD', strlen( $url ), $url,
159 - 8, 'HTTP/1.0', 0 );
160 -
161 - $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
162 - $htcpLen = 4 + $htcpDataLen + 2;
163 -
164 - // Note! Squid gets the bit order of the first
165 - // word wrong, wrt the RFC. Apparently no other
166 - // implementation exists, so adapt to Squid
167 - $htcpPacket = pack( 'nxxnCxNxxa*n',
168 - $htcpLen, $htcpDataLen, $htcpOpCLR,
169 - $htcpTransID, $htcpSpecifier, 2);
170 -
171 - // Send out
172 - wfDebug( "Purging URL $url via HTCP\n" );
173 - socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
174 - $wgHTCPMulticastAddress, $wgHTCPPort );
175 - }
176 - } else {
177 - $errstr = socket_strerror( socket_last_error() );
178 - wfDebug( __METHOD__ . "(): Error opening UDP socket: $errstr\n" );
179 - }
180 - wfProfileOut( __METHOD__ );
181 - }
182 -
183 - /**
184 - * Expand local URLs to fully-qualified URLs using the internal protocol
185 - * and host defined in $wgInternalServer. Input that's already fully-
186 - * qualified will be passed through unchanged.
187 - *
188 - * This is used to generate purge URLs that may be either local to the
189 - * main wiki or include a non-native host, such as images hosted on a
190 - * second internal server.
191 - *
192 - * Client functions should not need to call this.
193 - *
194 - * @return string
195 - */
196 - static function expand( $url ) {
197 - global $wgInternalServer, $wgServer;
198 - $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
199 - if( $url !== '' && $url[0] == '/' ) {
200 - return $server . $url;
201 - }
202 - return $url;
203 - }
204 -}
Index: trunk/phase3/includes/LinkCache.php
@@ -1,201 +0,0 @@
2 -<?php
3 -/**
4 - * Cache for article titles (prefixed DB keys) and ids linked from one source
5 - *
6 - * @ingroup Cache
7 - */
8 -class LinkCache {
9 - // Increment $mClassVer whenever old serialized versions of this class
10 - // becomes incompatible with the new version.
11 - private $mClassVer = 4;
12 -
13 - private $mGoodLinks, $mBadLinks;
14 - private $mForUpdate;
15 -
16 - /**
17 - * Get an instance of this class
18 - */
19 - static function &singleton() {
20 - static $instance;
21 - if ( !isset( $instance ) ) {
22 - $instance = new LinkCache;
23 - }
24 - return $instance;
25 - }
26 -
27 - function __construct() {
28 - $this->mForUpdate = false;
29 - $this->mGoodLinks = array();
30 - $this->mGoodLinkFields = array();
31 - $this->mBadLinks = array();
32 - }
33 -
34 - /**
35 - * General accessor to get/set whether SELECT FOR UPDATE should be used
36 - */
37 - public function forUpdate( $update = null ) {
38 - return wfSetVar( $this->mForUpdate, $update );
39 - }
40 -
41 - public function getGoodLinkID( $title ) {
42 - if ( array_key_exists( $title, $this->mGoodLinks ) ) {
43 - return $this->mGoodLinks[$title];
44 - } else {
45 - return 0;
46 - }
47 - }
48 -
49 - /**
50 - * Get a field of a title object from cache.
51 - * If this link is not good, it will return NULL.
52 - * @param $title Title
53 - * @param $field String: ('length','redirect','revision')
54 - * @return mixed
55 - */
56 - public function getGoodLinkFieldObj( $title, $field ) {
57 - $dbkey = $title->getPrefixedDbKey();
58 - if ( array_key_exists( $dbkey, $this->mGoodLinkFields ) ) {
59 - return $this->mGoodLinkFields[$dbkey][$field];
60 - } else {
61 - return null;
62 - }
63 - }
64 -
65 - public function isBadLink( $title ) {
66 - return array_key_exists( $title, $this->mBadLinks );
67 - }
68 -
69 - /**
70 - * Add a link for the title to the link cache
71 - *
72 - * @param $id Integer: page's ID
73 - * @param $title Title object
74 - * @param $len Integer: text's length
75 - * @param $redir Integer: whether the page is a redirect
76 - * @param $revision Integer: latest revision's ID
77 - */
78 - public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false ) {
79 - $dbkey = $title->getPrefixedDbKey();
80 - $this->mGoodLinks[$dbkey] = intval( $id );
81 - $this->mGoodLinkFields[$dbkey] = array(
82 - 'length' => intval( $len ),
83 - 'redirect' => intval( $redir ),
84 - 'revision' => intval( $revision ) );
85 - }
86 -
87 - public function addBadLinkObj( $title ) {
88 - $dbkey = $title->getPrefixedDbKey();
89 - if ( !$this->isBadLink( $dbkey ) ) {
90 - $this->mBadLinks[$dbkey] = 1;
91 - }
92 - }
93 -
94 - public function clearBadLink( $title ) {
95 - unset( $this->mBadLinks[$title] );
96 - }
97 -
98 - public function clearLink( $title ) {
99 - $dbkey = $title->getPrefixedDbKey();
100 - if( isset($this->mBadLinks[$dbkey]) ) {
101 - unset($this->mBadLinks[$dbkey]);
102 - }
103 - if( isset($this->mGoodLinks[$dbkey]) ) {
104 - unset($this->mGoodLinks[$dbkey]);
105 - }
106 - if( isset($this->mGoodLinkFields[$dbkey]) ) {
107 - unset($this->mGoodLinkFields[$dbkey]);
108 - }
109 - }
110 -
111 - public function getGoodLinks() { return $this->mGoodLinks; }
112 - public function getBadLinks() { return array_keys( $this->mBadLinks ); }
113 -
114 - /**
115 - * Add a title to the link cache, return the page_id or zero if non-existent
116 - *
117 - * @param $title String: title to add
118 - * @return Integer
119 - */
120 - public function addLink( $title ) {
121 - $nt = Title::newFromDBkey( $title );
122 - if( $nt ) {
123 - return $this->addLinkObj( $nt );
124 - } else {
125 - return 0;
126 - }
127 - }
128 -
129 - /**
130 - * Add a title to the link cache, return the page_id or zero if non-existent
131 - *
132 - * @param $nt Title object to add
133 - * @return Integer
134 - */
135 - public function addLinkObj( $nt ) {
136 - global $wgAntiLockFlags;
137 - wfProfileIn( __METHOD__ );
138 -
139 - $key = $nt->getPrefixedDBkey();
140 - if ( $this->isBadLink( $key ) || $nt->isExternal() ) {
141 - wfProfileOut( __METHOD__ );
142 - return 0;
143 - }
144 - $id = $this->getGoodLinkID( $key );
145 - if ( $id != 0 ) {
146 - wfProfileOut( __METHOD__ );
147 - return $id;
148 - }
149 -
150 - if ( $key === '' ) {
151 - wfProfileOut( __METHOD__ );
152 - return 0;
153 - }
154 -
155 - # Some fields heavily used for linking...
156 - if ( $this->mForUpdate ) {
157 - $db = wfGetDB( DB_MASTER );
158 - if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
159 - $options = array( 'FOR UPDATE' );
160 - } else {
161 - $options = array();
162 - }
163 - } else {
164 - $db = wfGetDB( DB_SLAVE );
165 - $options = array();
166 - }
167 -
168 - $s = $db->selectRow( 'page',
169 - array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
170 - array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
171 - __METHOD__, $options );
172 - # Set fields...
173 - if ( $s !== false ) {
174 - $id = intval( $s->page_id );
175 - $len = intval( $s->page_len );
176 - $redirect = intval( $s->page_is_redirect );
177 - $revision = intval( $s->page_latest );
178 - } else {
179 - $id = 0;
180 - $len = -1;
181 - $redirect = 0;
182 - $revision = 0;
183 - }
184 -
185 - if ( $id == 0 ) {
186 - $this->addBadLinkObj( $nt );
187 - } else {
188 - $this->addGoodLinkObj( $id, $nt, $len, $redirect, $revision );
189 - }
190 - wfProfileOut( __METHOD__ );
191 - return $id;
192 - }
193 -
194 - /**
195 - * Clears cache
196 - */
197 - public function clear() {
198 - $this->mGoodLinks = array();
199 - $this->mGoodLinkFields = array();
200 - $this->mBadLinks = array();
201 - }
202 -}
Index: trunk/phase3/includes/AutoLoader.php
@@ -133,8 +133,6 @@
134134 'LegacyTemplate' => 'includes/SkinLegacy.php',
135135 'License' => 'includes/Licenses.php',
136136 'Licenses' => 'includes/Licenses.php',
137 - 'LinkBatch' => 'includes/LinkBatch.php',
138 - 'LinkCache' => 'includes/LinkCache.php',
139137 'Linker' => 'includes/Linker.php',
140138 'LinkFilter' => 'includes/LinkFilter.php',
141139 'LinksUpdate' => 'includes/LinksUpdate.php',
@@ -153,7 +151,6 @@
154152 'MediaWiki_I18N' => 'includes/SkinTemplate.php',
155153 'Message' => 'includes/Message.php',
156154 'MessageBlobStore' => 'includes/MessageBlobStore.php',
157 - 'MessageCache' => 'includes/MessageCache.php',
158155 'MimeMagic' => 'includes/MimeMagic.php',
159156 'MWException' => 'includes/Exception.php',
160157 'MWFunction' => 'includes/MWFunction.php',
@@ -211,7 +208,6 @@
212209 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php',
213210 'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
214211 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
215 - 'SquidUpdate' => 'includes/SquidUpdate.php',
216212 'Status' => 'includes/Status.php',
217213 'StringUtils' => 'includes/StringUtils.php',
218214 'StubContLang' => 'includes/StubObject.php',
@@ -355,6 +351,10 @@
356352 'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
357353 'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
358354 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
 355+ 'LinkBatch' => 'includes/cache/LinkBatch.php',
 356+ 'LinkCache' => 'includes/cache/LinkCache.php',
 357+ 'MessageCache' => 'includes/cache/MessageCache.php',
 358+ 'SquidUpdate' => 'includes/cache/SquidUpdate.php',
359359 'TitleDependency' => 'includes/cache/CacheDependency.php',
360360 'TitleListDependency' => 'includes/cache/CacheDependency.php',
361361
Index: trunk/phase3/includes/cache/MemcachedSessions.php
@@ -0,0 +1,98 @@
 2+<?php
 3+/**
 4+ * This file gets included if $wgSessionsInMemcache is set in the config.
 5+ * It redirects session handling functions to store their data in memcached
 6+ * instead of the local filesystem. Depending on circumstances, it may also
 7+ * be necessary to change the cookie settings to work across hostnames.
 8+ * See: http://www.php.net/manual/en/function.session-set-save-handler.php
 9+ *
 10+ * @file
 11+ * @ingroup Cache
 12+ */
 13+
 14+/**
 15+ * Get a cache key for the given session id.
 16+ *
 17+ * @param $id String: session id
 18+ * @return String: cache key
 19+ */
 20+function memsess_key( $id ) {
 21+ return wfMemcKey( 'session', $id );
 22+}
 23+
 24+/**
 25+ * Callback when opening a session.
 26+ * NOP: $wgMemc should be set up already.
 27+ *
 28+ * @param $save_path String: path used to store session files, unused
 29+ * @param $session_name String: session name
 30+ * @return Boolean: success
 31+ */
 32+function memsess_open( $save_path, $session_name ) {
 33+ return true;
 34+}
 35+
 36+/**
 37+ * Callback when closing a session.
 38+ * NOP.
 39+ *
 40+ * @return Boolean: success
 41+ */
 42+function memsess_close() {
 43+ return true;
 44+}
 45+
 46+/**
 47+ * Callback when reading session data.
 48+ *
 49+ * @param $id String: session id
 50+ * @return Mixed: session data
 51+ */
 52+function memsess_read( $id ) {
 53+ global $wgMemc;
 54+ $data = $wgMemc->get( memsess_key( $id ) );
 55+ if( ! $data ) return '';
 56+ return $data;
 57+}
 58+
 59+/**
 60+ * Callback when writing session data.
 61+ *
 62+ * @param $id String: session id
 63+ * @param $data Mixed: session data
 64+ * @return Boolean: success
 65+ */
 66+function memsess_write( $id, $data ) {
 67+ global $wgMemc;
 68+ $wgMemc->set( memsess_key( $id ), $data, 3600 );
 69+ return true;
 70+}
 71+
 72+/**
 73+ * Callback to destroy a session when calling session_destroy().
 74+ *
 75+ * @param $id String: session id
 76+ * @return Boolean: success
 77+ */
 78+function memsess_destroy( $id ) {
 79+ global $wgMemc;
 80+
 81+ $wgMemc->delete( memsess_key( $id ) );
 82+ return true;
 83+}
 84+
 85+/**
 86+ * Callback to execute garbage collection.
 87+ * NOP: Memcached performs garbage collection.
 88+ *
 89+ * @param $maxlifetime Integer: maximum session life time
 90+ * @return Boolean: success
 91+ */
 92+function memsess_gc( $maxlifetime ) {
 93+ return true;
 94+}
 95+
 96+function memsess_write_close() {
 97+ session_write_close();
 98+}
 99+
Property changes on: trunk/phase3/includes/cache/MemcachedSessions.php
___________________________________________________________________
Added: svn:eol-style
1100 + native
Added: svn:keywords
2101 + Author Date Id Revision
Index: trunk/phase3/includes/cache/LinkBatch.php
@@ -0,0 +1,185 @@
 2+<?php
 3+
 4+/**
 5+ * Class representing a list of titles
 6+ * The execute() method checks them all for existence and adds them to a LinkCache object
 7+ *
 8+ * @ingroup Cache
 9+ */
 10+class LinkBatch {
 11+ /**
 12+ * 2-d array, first index namespace, second index dbkey, value arbitrary
 13+ */
 14+ var $data = array();
 15+
 16+ /**
 17+ * For debugging which method is using this class.
 18+ */
 19+ protected $caller;
 20+
 21+ function __construct( $arr = array() ) {
 22+ foreach( $arr as $item ) {
 23+ $this->addObj( $item );
 24+ }
 25+ }
 26+
 27+ /**
 28+ * Use ->setCaller( __METHOD__ ) to indicate which code is using this
 29+ * class. Only used in debugging output.
 30+ * @since 1.17
 31+ */
 32+ public function setCaller( $caller ) {
 33+ $this->caller = $caller;
 34+ }
 35+
 36+ /**
 37+ * @param $title Title
 38+ * @return void
 39+ */
 40+ public function addObj( $title ) {
 41+ if ( is_object( $title ) ) {
 42+ $this->add( $title->getNamespace(), $title->getDBkey() );
 43+ } else {
 44+ wfDebug( "Warning: LinkBatch::addObj got invalid title object\n" );
 45+ }
 46+ }
 47+
 48+ public function add( $ns, $dbkey ) {
 49+ if ( $ns < 0 ) {
 50+ return;
 51+ }
 52+ if ( !array_key_exists( $ns, $this->data ) ) {
 53+ $this->data[$ns] = array();
 54+ }
 55+
 56+ $this->data[$ns][str_replace( ' ', '_', $dbkey )] = 1;
 57+ }
 58+
 59+ /**
 60+ * Set the link list to a given 2-d array
 61+ * First key is the namespace, second is the DB key, value arbitrary
 62+ */
 63+ public function setArray( $array ) {
 64+ $this->data = $array;
 65+ }
 66+
 67+ /**
 68+ * Returns true if no pages have been added, false otherwise.
 69+ */
 70+ public function isEmpty() {
 71+ return ($this->getSize() == 0);
 72+ }
 73+
 74+ /**
 75+ * Returns the size of the batch.
 76+ */
 77+ public function getSize() {
 78+ return count( $this->data );
 79+ }
 80+
 81+ /**
 82+ * Do the query and add the results to the LinkCache object
 83+ * Return an array mapping PDBK to ID
 84+ */
 85+ public function execute() {
 86+ $linkCache = LinkCache::singleton();
 87+ return $this->executeInto( $linkCache );
 88+ }
 89+
 90+ /**
 91+ * Do the query and add the results to a given LinkCache object
 92+ * Return an array mapping PDBK to ID
 93+ */
 94+ protected function executeInto( &$cache ) {
 95+ wfProfileIn( __METHOD__ );
 96+ $res = $this->doQuery();
 97+ $ids = $this->addResultToCache( $cache, $res );
 98+ $this->doGenderQuery();
 99+ wfProfileOut( __METHOD__ );
 100+ return $ids;
 101+ }
 102+
 103+ /**
 104+ * Add a ResultWrapper containing IDs and titles to a LinkCache object.
 105+ * As normal, titles will go into the static Title cache field.
 106+ * This function *also* stores extra fields of the title used for link
 107+ * parsing to avoid extra DB queries.
 108+ */
 109+ public function addResultToCache( $cache, $res ) {
 110+ if ( !$res ) {
 111+ return array();
 112+ }
 113+
 114+ // For each returned entry, add it to the list of good links, and remove it from $remaining
 115+
 116+ $ids = array();
 117+ $remaining = $this->data;
 118+ foreach ( $res as $row ) {
 119+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
 120+ $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
 121+ $ids[$title->getPrefixedDBkey()] = $row->page_id;
 122+ unset( $remaining[$row->page_namespace][$row->page_title] );
 123+ }
 124+
 125+ // The remaining links in $data are bad links, register them as such
 126+ foreach ( $remaining as $ns => $dbkeys ) {
 127+ foreach ( $dbkeys as $dbkey => $unused ) {
 128+ $title = Title::makeTitle( $ns, $dbkey );
 129+ $cache->addBadLinkObj( $title );
 130+ $ids[$title->getPrefixedDBkey()] = 0;
 131+ }
 132+ }
 133+ return $ids;
 134+ }
 135+
 136+ /**
 137+ * Perform the existence test query, return a ResultWrapper with page_id fields
 138+ */
 139+ public function doQuery() {
 140+ if ( $this->isEmpty() ) {
 141+ return false;
 142+ }
 143+ wfProfileIn( __METHOD__ );
 144+
 145+ // This is similar to LinkHolderArray::replaceInternal
 146+ $dbr = wfGetDB( DB_SLAVE );
 147+ $table = 'page';
 148+ $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len',
 149+ 'page_is_redirect', 'page_latest' );
 150+ $conds = $this->constructSet( 'page', $dbr );
 151+
 152+ // Do query
 153+ $caller = __METHOD__;
 154+ if ( strval( $this->caller ) !== '' ) {
 155+ $caller .= " (for {$this->caller})";
 156+ }
 157+ $res = $dbr->select( $table, $fields, $conds, $caller );
 158+ wfProfileOut( __METHOD__ );
 159+ return $res;
 160+ }
 161+
 162+ public function doGenderQuery() {
 163+ if ( $this->isEmpty() ) {
 164+ return false;
 165+ }
 166+
 167+ global $wgContLang;
 168+ if ( !$wgContLang->needsGenderDistinction() ) {
 169+ return false;
 170+ }
 171+
 172+ $genderCache = GenderCache::singleton();
 173+ $genderCache->dolinkBatch( $this->data, $this->caller );
 174+ }
 175+
 176+ /**
 177+ * Construct a WHERE clause which will match all the given titles.
 178+ *
 179+ * @param $prefix String: the appropriate table's field name prefix ('page', 'pl', etc)
 180+ * @param $db DatabaseBase object to use
 181+ * @return mixed string with SQL where clause fragment, or false if no items.
 182+ */
 183+ public function constructSet( $prefix, $db ) {
 184+ return $db->makeWhereFrom2d( $this->data, "{$prefix}_namespace", "{$prefix}_title" );
 185+ }
 186+}
Property changes on: trunk/phase3/includes/cache/LinkBatch.php
___________________________________________________________________
Added: svn:eol-style
1187 + native
Added: svn:keywords
2188 + Author Date Id Revision
Index: trunk/phase3/includes/cache/LinkCache.php
@@ -0,0 +1,201 @@
 2+<?php
 3+/**
 4+ * Cache for article titles (prefixed DB keys) and ids linked from one source
 5+ *
 6+ * @ingroup Cache
 7+ */
 8+class LinkCache {
 9+ // Increment $mClassVer whenever old serialized versions of this class
 10+ // becomes incompatible with the new version.
 11+ private $mClassVer = 4;
 12+
 13+ private $mGoodLinks, $mBadLinks;
 14+ private $mForUpdate;
 15+
 16+ /**
 17+ * Get an instance of this class
 18+ */
 19+ static function &singleton() {
 20+ static $instance;
 21+ if ( !isset( $instance ) ) {
 22+ $instance = new LinkCache;
 23+ }
 24+ return $instance;
 25+ }
 26+
 27+ function __construct() {
 28+ $this->mForUpdate = false;
 29+ $this->mGoodLinks = array();
 30+ $this->mGoodLinkFields = array();
 31+ $this->mBadLinks = array();
 32+ }
 33+
 34+ /**
 35+ * General accessor to get/set whether SELECT FOR UPDATE should be used
 36+ */
 37+ public function forUpdate( $update = null ) {
 38+ return wfSetVar( $this->mForUpdate, $update );
 39+ }
 40+
 41+ public function getGoodLinkID( $title ) {
 42+ if ( array_key_exists( $title, $this->mGoodLinks ) ) {
 43+ return $this->mGoodLinks[$title];
 44+ } else {
 45+ return 0;
 46+ }
 47+ }
 48+
 49+ /**
 50+ * Get a field of a title object from cache.
 51+ * If this link is not good, it will return NULL.
 52+ * @param $title Title
 53+ * @param $field String: ('length','redirect','revision')
 54+ * @return mixed
 55+ */
 56+ public function getGoodLinkFieldObj( $title, $field ) {
 57+ $dbkey = $title->getPrefixedDbKey();
 58+ if ( array_key_exists( $dbkey, $this->mGoodLinkFields ) ) {
 59+ return $this->mGoodLinkFields[$dbkey][$field];
 60+ } else {
 61+ return null;
 62+ }
 63+ }
 64+
 65+ public function isBadLink( $title ) {
 66+ return array_key_exists( $title, $this->mBadLinks );
 67+ }
 68+
 69+ /**
 70+ * Add a link for the title to the link cache
 71+ *
 72+ * @param $id Integer: page's ID
 73+ * @param $title Title object
 74+ * @param $len Integer: text's length
 75+ * @param $redir Integer: whether the page is a redirect
 76+ * @param $revision Integer: latest revision's ID
 77+ */
 78+ public function addGoodLinkObj( $id, $title, $len = -1, $redir = null, $revision = false ) {
 79+ $dbkey = $title->getPrefixedDbKey();
 80+ $this->mGoodLinks[$dbkey] = intval( $id );
 81+ $this->mGoodLinkFields[$dbkey] = array(
 82+ 'length' => intval( $len ),
 83+ 'redirect' => intval( $redir ),
 84+ 'revision' => intval( $revision ) );
 85+ }
 86+
 87+ public function addBadLinkObj( $title ) {
 88+ $dbkey = $title->getPrefixedDbKey();
 89+ if ( !$this->isBadLink( $dbkey ) ) {
 90+ $this->mBadLinks[$dbkey] = 1;
 91+ }
 92+ }
 93+
 94+ public function clearBadLink( $title ) {
 95+ unset( $this->mBadLinks[$title] );
 96+ }
 97+
 98+ public function clearLink( $title ) {
 99+ $dbkey = $title->getPrefixedDbKey();
 100+ if( isset($this->mBadLinks[$dbkey]) ) {
 101+ unset($this->mBadLinks[$dbkey]);
 102+ }
 103+ if( isset($this->mGoodLinks[$dbkey]) ) {
 104+ unset($this->mGoodLinks[$dbkey]);
 105+ }
 106+ if( isset($this->mGoodLinkFields[$dbkey]) ) {
 107+ unset($this->mGoodLinkFields[$dbkey]);
 108+ }
 109+ }
 110+
 111+ public function getGoodLinks() { return $this->mGoodLinks; }
 112+ public function getBadLinks() { return array_keys( $this->mBadLinks ); }
 113+
 114+ /**
 115+ * Add a title to the link cache, return the page_id or zero if non-existent
 116+ *
 117+ * @param $title String: title to add
 118+ * @return Integer
 119+ */
 120+ public function addLink( $title ) {
 121+ $nt = Title::newFromDBkey( $title );
 122+ if( $nt ) {
 123+ return $this->addLinkObj( $nt );
 124+ } else {
 125+ return 0;
 126+ }
 127+ }
 128+
 129+ /**
 130+ * Add a title to the link cache, return the page_id or zero if non-existent
 131+ *
 132+ * @param $nt Title object to add
 133+ * @return Integer
 134+ */
 135+ public function addLinkObj( $nt ) {
 136+ global $wgAntiLockFlags;
 137+ wfProfileIn( __METHOD__ );
 138+
 139+ $key = $nt->getPrefixedDBkey();
 140+ if ( $this->isBadLink( $key ) || $nt->isExternal() ) {
 141+ wfProfileOut( __METHOD__ );
 142+ return 0;
 143+ }
 144+ $id = $this->getGoodLinkID( $key );
 145+ if ( $id != 0 ) {
 146+ wfProfileOut( __METHOD__ );
 147+ return $id;
 148+ }
 149+
 150+ if ( $key === '' ) {
 151+ wfProfileOut( __METHOD__ );
 152+ return 0;
 153+ }
 154+
 155+ # Some fields heavily used for linking...
 156+ if ( $this->mForUpdate ) {
 157+ $db = wfGetDB( DB_MASTER );
 158+ if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
 159+ $options = array( 'FOR UPDATE' );
 160+ } else {
 161+ $options = array();
 162+ }
 163+ } else {
 164+ $db = wfGetDB( DB_SLAVE );
 165+ $options = array();
 166+ }
 167+
 168+ $s = $db->selectRow( 'page',
 169+ array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
 170+ array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
 171+ __METHOD__, $options );
 172+ # Set fields...
 173+ if ( $s !== false ) {
 174+ $id = intval( $s->page_id );
 175+ $len = intval( $s->page_len );
 176+ $redirect = intval( $s->page_is_redirect );
 177+ $revision = intval( $s->page_latest );
 178+ } else {
 179+ $id = 0;
 180+ $len = -1;
 181+ $redirect = 0;
 182+ $revision = 0;
 183+ }
 184+
 185+ if ( $id == 0 ) {
 186+ $this->addBadLinkObj( $nt );
 187+ } else {
 188+ $this->addGoodLinkObj( $id, $nt, $len, $redirect, $revision );
 189+ }
 190+ wfProfileOut( __METHOD__ );
 191+ return $id;
 192+ }
 193+
 194+ /**
 195+ * Clears cache
 196+ */
 197+ public function clear() {
 198+ $this->mGoodLinks = array();
 199+ $this->mGoodLinkFields = array();
 200+ $this->mBadLinks = array();
 201+ }
 202+}
Property changes on: trunk/phase3/includes/cache/LinkCache.php
___________________________________________________________________
Added: svn:eol-style
1203 + native
Added: svn:keywords
2204 + Author Date Id Revision
Index: trunk/phase3/includes/cache/MessageCache.php
@@ -0,0 +1,939 @@
 2+<?php
 3+/**
 4+ * @file
 5+ * @ingroup Cache
 6+ */
 7+
 8+/**
 9+ *
 10+ */
 11+define( 'MSG_LOAD_TIMEOUT', 60 );
 12+define( 'MSG_LOCK_TIMEOUT', 10 );
 13+define( 'MSG_WAIT_TIMEOUT', 10 );
 14+define( 'MSG_CACHE_VERSION', 1 );
 15+
 16+/**
 17+ * Message cache
 18+ * Performs various MediaWiki namespace-related functions
 19+ * @ingroup Cache
 20+ */
 21+class MessageCache {
 22+ /**
 23+ * Process local cache of loaded messages that are defined in
 24+ * MediaWiki namespace. First array level is a language code,
 25+ * second level is message key and the values are either message
 26+ * content prefixed with space, or !NONEXISTENT for negative
 27+ * caching.
 28+ */
 29+ protected $mCache;
 30+
 31+ // Should mean that database cannot be used, but check
 32+ protected $mDisable;
 33+
 34+ /// Lifetime for cache, used by object caching
 35+ protected $mExpiry;
 36+
 37+ /**
 38+ * Message cache has it's own parser which it uses to transform
 39+ * messages.
 40+ */
 41+ protected $mParserOptions, $mParser;
 42+
 43+ /// Variable for tracking which variables are already loaded
 44+ protected $mLoadedLanguages = array();
 45+
 46+ /**
 47+ * Used for automatic detection of most used messages.
 48+ */
 49+ protected $mRequestedMessages = array();
 50+
 51+ /**
 52+ * How long the message request counts are stored. Longer period gives
 53+ * better sample, but also takes longer to adapt changes. The counts
 54+ * are aggregrated per day, regardless of the value of this variable.
 55+ */
 56+ protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
 57+
 58+ /**
 59+ * Filter the tail of less used messages that are requested more seldom
 60+ * than this factor times the number of request of most requested message.
 61+ * These messages are not loaded in the default set, but are still cached
 62+ * individually on demand with the normal cache expiry time.
 63+ */
 64+ protected static $mAdaptiveInclusionThreshold = 0.05;
 65+
 66+ /**
 67+ * Singleton instance
 68+ */
 69+ private static $instance;
 70+
 71+ /**
 72+ * @var bool
 73+ */
 74+ protected $mInParser = false;
 75+
 76+ /**
 77+ * Get the signleton instance of this class
 78+ *
 79+ * @since 1.18
 80+ * @return MessageCache object
 81+ */
 82+ public static function singleton() {
 83+ if ( is_null( self::$instance ) ) {
 84+ global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
 85+ self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
 86+ }
 87+ return self::$instance;
 88+ }
 89+
 90+ /**
 91+ * Destroy the singleton instance
 92+ *
 93+ * @since 1.18
 94+ */
 95+ public static function destroyInstance() {
 96+ self::$instance = null;
 97+ }
 98+
 99+ function __construct( $memCached, $useDB, $expiry ) {
 100+ if ( !$memCached ) {
 101+ $memCached = wfGetCache( CACHE_NONE );
 102+ }
 103+
 104+ $this->mMemc = $memCached;
 105+ $this->mDisable = !$useDB;
 106+ $this->mExpiry = $expiry;
 107+ }
 108+
 109+ /**
 110+ * ParserOptions is lazy initialised.
 111+ *
 112+ * @return ParserOptions
 113+ */
 114+ function getParserOptions() {
 115+ if ( !$this->mParserOptions ) {
 116+ $this->mParserOptions = new ParserOptions;
 117+ }
 118+ return $this->mParserOptions;
 119+ }
 120+
 121+ /**
 122+ * Try to load the cache from a local file.
 123+ * Actual format of the file depends on the $wgLocalMessageCacheSerialized
 124+ * setting.
 125+ *
 126+ * @param $hash String: the hash of contents, to check validity.
 127+ * @param $code Mixed: Optional language code, see documenation of load().
 128+ * @return false on failure.
 129+ */
 130+ function loadFromLocal( $hash, $code ) {
 131+ global $wgCacheDirectory, $wgLocalMessageCacheSerialized;
 132+
 133+ $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
 134+
 135+ # Check file existence
 136+ wfSuppressWarnings();
 137+ $file = fopen( $filename, 'r' );
 138+ wfRestoreWarnings();
 139+ if ( !$file ) {
 140+ return false; // No cache file
 141+ }
 142+
 143+ if ( $wgLocalMessageCacheSerialized ) {
 144+ // Check to see if the file has the hash specified
 145+ $localHash = fread( $file, 32 );
 146+ if ( $hash === $localHash ) {
 147+ // All good, get the rest of it
 148+ $serialized = '';
 149+ while ( !feof( $file ) ) {
 150+ $serialized .= fread( $file, 100000 );
 151+ }
 152+ fclose( $file );
 153+ return $this->setCache( unserialize( $serialized ), $code );
 154+ } else {
 155+ fclose( $file );
 156+ return false; // Wrong hash
 157+ }
 158+ } else {
 159+ $localHash = substr( fread( $file, 40 ), 8 );
 160+ fclose( $file );
 161+ if ( $hash != $localHash ) {
 162+ return false; // Wrong hash
 163+ }
 164+
 165+ # Require overwrites the member variable or just shadows it?
 166+ require( $filename );
 167+ return $this->setCache( $this->mCache, $code );
 168+ }
 169+ }
 170+
 171+ /**
 172+ * Save the cache to a local file.
 173+ */
 174+ function saveToLocal( $serialized, $hash, $code ) {
 175+ global $wgCacheDirectory;
 176+
 177+ $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
 178+ wfMkdirParents( $wgCacheDirectory ); // might fail
 179+
 180+ wfSuppressWarnings();
 181+ $file = fopen( $filename, 'w' );
 182+ wfRestoreWarnings();
 183+
 184+ if ( !$file ) {
 185+ wfDebug( "Unable to open local cache file for writing\n" );
 186+ return;
 187+ }
 188+
 189+ fwrite( $file, $hash . $serialized );
 190+ fclose( $file );
 191+ @chmod( $filename, 0666 );
 192+ }
 193+
 194+ function saveToScript( $array, $hash, $code ) {
 195+ global $wgCacheDirectory;
 196+
 197+ $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
 198+ $tempFilename = $filename . '.tmp';
 199+ wfMkdirParents( $wgCacheDirectory ); // might fail
 200+
 201+ wfSuppressWarnings();
 202+ $file = fopen( $tempFilename, 'w' );
 203+ wfRestoreWarnings();
 204+
 205+ if ( !$file ) {
 206+ wfDebug( "Unable to open local cache file for writing\n" );
 207+ return;
 208+ }
 209+
 210+ fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
 211+
 212+ foreach ( $array as $key => $message ) {
 213+ $key = $this->escapeForScript( $key );
 214+ $message = $this->escapeForScript( $message );
 215+ fwrite( $file, "'$key' => '$message',\n" );
 216+ }
 217+
 218+ fwrite( $file, ");\n?>" );
 219+ fclose( $file);
 220+ rename( $tempFilename, $filename );
 221+ }
 222+
 223+ function escapeForScript( $string ) {
 224+ $string = str_replace( '\\', '\\\\', $string );
 225+ $string = str_replace( '\'', '\\\'', $string );
 226+ return $string;
 227+ }
 228+
 229+ /**
 230+ * Set the cache to $cache, if it is valid. Otherwise set the cache to false.
 231+ *
 232+ * @return bool
 233+ */
 234+ function setCache( $cache, $code ) {
 235+ if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
 236+ $this->mCache[$code] = $cache;
 237+ return true;
 238+ } else {
 239+ return false;
 240+ }
 241+ }
 242+
 243+ /**
 244+ * Loads messages from caches or from database in this order:
 245+ * (1) local message cache (if $wgUseLocalMessageCache is enabled)
 246+ * (2) memcached
 247+ * (3) from the database.
 248+ *
 249+ * When succesfully loading from (2) or (3), all higher level caches are
 250+ * updated for the newest version.
 251+ *
 252+ * Nothing is loaded if member variable mDisable is true, either manually
 253+ * set by calling code or if message loading fails (is this possible?).
 254+ *
 255+ * Returns true if cache is already populated or it was succesfully populated,
 256+ * or false if populating empty cache fails. Also returns true if MessageCache
 257+ * is disabled.
 258+ *
 259+ * @param $code String: language to which load messages
 260+ */
 261+ function load( $code = false ) {
 262+ global $wgUseLocalMessageCache;
 263+
 264+ if( !is_string( $code ) ) {
 265+ # This isn't really nice, so at least make a note about it and try to
 266+ # fall back
 267+ wfDebug( __METHOD__ . " called without providing a language code\n" );
 268+ $code = 'en';
 269+ }
 270+
 271+ # Don't do double loading...
 272+ if ( isset( $this->mLoadedLanguages[$code] ) ) {
 273+ return true;
 274+ }
 275+
 276+ # 8 lines of code just to say (once) that message cache is disabled
 277+ if ( $this->mDisable ) {
 278+ static $shownDisabled = false;
 279+ if ( !$shownDisabled ) {
 280+ wfDebug( __METHOD__ . ": disabled\n" );
 281+ $shownDisabled = true;
 282+ }
 283+ return true;
 284+ }
 285+
 286+ # Loading code starts
 287+ wfProfileIn( __METHOD__ );
 288+ $success = false; # Keep track of success
 289+ $where = array(); # Debug info, delayed to avoid spamming debug log too much
 290+ $cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
 291+
 292+ # (1) local cache
 293+ # Hash of the contents is stored in memcache, to detect if local cache goes
 294+ # out of date (due to update in other thread?)
 295+ if ( $wgUseLocalMessageCache ) {
 296+ wfProfileIn( __METHOD__ . '-fromlocal' );
 297+
 298+ $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
 299+ if ( $hash ) {
 300+ $success = $this->loadFromLocal( $hash, $code );
 301+ if ( $success ) $where[] = 'got from local cache';
 302+ }
 303+ wfProfileOut( __METHOD__ . '-fromlocal' );
 304+ }
 305+
 306+ # (2) memcache
 307+ # Fails if nothing in cache, or in the wrong version.
 308+ if ( !$success ) {
 309+ wfProfileIn( __METHOD__ . '-fromcache' );
 310+ $cache = $this->mMemc->get( $cacheKey );
 311+ $success = $this->setCache( $cache, $code );
 312+ if ( $success ) {
 313+ $where[] = 'got from global cache';
 314+ $this->saveToCaches( $cache, false, $code );
 315+ }
 316+ wfProfileOut( __METHOD__ . '-fromcache' );
 317+ }
 318+
 319+ # (3)
 320+ # Nothing in caches... so we need create one and store it in caches
 321+ if ( !$success ) {
 322+ $where[] = 'cache is empty';
 323+ $where[] = 'loading from database';
 324+
 325+ $this->lock( $cacheKey );
 326+
 327+ # Limit the concurrency of loadFromDB to a single process
 328+ # This prevents the site from going down when the cache expires
 329+ $statusKey = wfMemcKey( 'messages', $code, 'status' );
 330+ $success = $this->mMemc->add( $statusKey, 'loading', MSG_LOAD_TIMEOUT );
 331+ if ( $success ) {
 332+ $cache = $this->loadFromDB( $code );
 333+ $success = $this->setCache( $cache, $code );
 334+ }
 335+ if ( $success ) {
 336+ $success = $this->saveToCaches( $cache, true, $code );
 337+ if ( $success ) {
 338+ $this->mMemc->delete( $statusKey );
 339+ } else {
 340+ $this->mMemc->set( $statusKey, 'error', 60 * 5 );
 341+ wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
 342+ }
 343+ }
 344+ $this->unlock($cacheKey);
 345+ }
 346+
 347+ if ( !$success ) {
 348+ # Bad luck... this should not happen
 349+ $where[] = 'loading FAILED - cache is disabled';
 350+ $info = implode( ', ', $where );
 351+ wfDebug( __METHOD__ . ": Loading $code... $info\n" );
 352+ $this->mDisable = true;
 353+ $this->mCache = false;
 354+ } else {
 355+ # All good, just record the success
 356+ $info = implode( ', ', $where );
 357+ wfDebug( __METHOD__ . ": Loading $code... $info\n" );
 358+ $this->mLoadedLanguages[$code] = true;
 359+ }
 360+ wfProfileOut( __METHOD__ );
 361+ return $success;
 362+ }
 363+
 364+ /**
 365+ * Loads cacheable messages from the database. Messages bigger than
 366+ * $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
 367+ * on-demand from the database later.
 368+ *
 369+ * @param $code String: language code.
 370+ * @return Array: loaded messages for storing in caches.
 371+ */
 372+ function loadFromDB( $code ) {
 373+ wfProfileIn( __METHOD__ );
 374+ global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
 375+ $dbr = wfGetDB( DB_SLAVE );
 376+ $cache = array();
 377+
 378+ # Common conditions
 379+ $conds = array(
 380+ 'page_is_redirect' => 0,
 381+ 'page_namespace' => NS_MEDIAWIKI,
 382+ );
 383+
 384+ $mostused = array();
 385+ if ( $wgAdaptiveMessageCache ) {
 386+ $mostused = $this->getMostUsedMessages();
 387+ if ( $code !== $wgLanguageCode ) {
 388+ foreach ( $mostused as $key => $value ) {
 389+ $mostused[$key] = "$value/$code";
 390+ }
 391+ }
 392+ }
 393+
 394+ if ( count( $mostused ) ) {
 395+ $conds['page_title'] = $mostused;
 396+ } elseif ( $code !== $wgLanguageCode ) {
 397+ $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
 398+ } else {
 399+ # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
 400+ # other than language code.
 401+ $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
 402+ }
 403+
 404+ # Conditions to fetch oversized pages to ignore them
 405+ $bigConds = $conds;
 406+ $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
 407+
 408+ # Load titles for all oversized pages in the MediaWiki namespace
 409+ $res = $dbr->select( 'page', 'page_title', $bigConds, __METHOD__ . "($code)-big" );
 410+ foreach ( $res as $row ) {
 411+ $cache[$row->page_title] = '!TOO BIG';
 412+ }
 413+
 414+ # Conditions to load the remaining pages with their contents
 415+ $smallConds = $conds;
 416+ $smallConds[] = 'page_latest=rev_id';
 417+ $smallConds[] = 'rev_text_id=old_id';
 418+ $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
 419+
 420+ $res = $dbr->select(
 421+ array( 'page', 'revision', 'text' ),
 422+ array( 'page_title', 'old_text', 'old_flags' ),
 423+ $smallConds,
 424+ __METHOD__ . "($code)-small"
 425+ );
 426+
 427+ foreach ( $res as $row ) {
 428+ $cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
 429+ }
 430+
 431+ foreach ( $mostused as $key ) {
 432+ if ( !isset( $cache[$key] ) ) {
 433+ $cache[$key] = '!NONEXISTENT';
 434+ }
 435+ }
 436+
 437+ $cache['VERSION'] = MSG_CACHE_VERSION;
 438+ wfProfileOut( __METHOD__ );
 439+ return $cache;
 440+ }
 441+
 442+ /**
 443+ * Updates cache as necessary when message page is changed
 444+ *
 445+ * @param $title String: name of the page changed.
 446+ * @param $text Mixed: new contents of the page.
 447+ */
 448+ public function replace( $title, $text ) {
 449+ global $wgMaxMsgCacheEntrySize;
 450+ wfProfileIn( __METHOD__ );
 451+
 452+ if ( $this->mDisable ) {
 453+ wfProfileOut( __METHOD__ );
 454+ return;
 455+ }
 456+
 457+ list( $msg, $code ) = $this->figureMessage( $title );
 458+
 459+ $cacheKey = wfMemcKey( 'messages', $code );
 460+ $this->load( $code );
 461+ $this->lock( $cacheKey );
 462+
 463+ $titleKey = wfMemcKey( 'messages', 'individual', $title );
 464+
 465+ if ( $text === false ) {
 466+ # Article was deleted
 467+ $this->mCache[$code][$title] = '!NONEXISTENT';
 468+ $this->mMemc->delete( $titleKey );
 469+ } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
 470+ # Check for size
 471+ $this->mCache[$code][$title] = '!TOO BIG';
 472+ $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
 473+ } else {
 474+ $this->mCache[$code][$title] = ' ' . $text;
 475+ $this->mMemc->delete( $titleKey );
 476+ }
 477+
 478+ # Update caches
 479+ $this->saveToCaches( $this->mCache[$code], true, $code );
 480+ $this->unlock( $cacheKey );
 481+
 482+ // Also delete cached sidebar... just in case it is affected
 483+ $codes = array( $code );
 484+ if ( $code === 'en' ) {
 485+ // Delete all sidebars, like for example on action=purge on the
 486+ // sidebar messages
 487+ $codes = array_keys( Language::getLanguageNames() );
 488+ }
 489+
 490+ global $parserMemc;
 491+ foreach ( $codes as $code ) {
 492+ $sidebarKey = wfMemcKey( 'sidebar', $code );
 493+ $parserMemc->delete( $sidebarKey );
 494+ }
 495+
 496+ // Update the message in the message blob store
 497+ global $wgContLang;
 498+ MessageBlobStore::updateMessage( $wgContLang->lcfirst( $msg ) );
 499+
 500+ wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
 501+
 502+ wfProfileOut( __METHOD__ );
 503+ }
 504+
 505+ /**
 506+ * Shortcut to update caches.
 507+ *
 508+ * @param $cache Array: cached messages with a version.
 509+ * @param $memc Bool: Wether to update or not memcache.
 510+ * @param $code String: Language code.
 511+ * @return False on somekind of error.
 512+ */
 513+ protected function saveToCaches( $cache, $memc = true, $code = false ) {
 514+ wfProfileIn( __METHOD__ );
 515+ global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized;
 516+
 517+ $cacheKey = wfMemcKey( 'messages', $code );
 518+
 519+ if ( $memc ) {
 520+ $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry );
 521+ } else {
 522+ $success = true;
 523+ }
 524+
 525+ # Save to local cache
 526+ if ( $wgUseLocalMessageCache ) {
 527+ $serialized = serialize( $cache );
 528+ $hash = md5( $serialized );
 529+ $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry );
 530+ if ($wgLocalMessageCacheSerialized) {
 531+ $this->saveToLocal( $serialized, $hash, $code );
 532+ } else {
 533+ $this->saveToScript( $cache, $hash, $code );
 534+ }
 535+ }
 536+
 537+ wfProfileOut( __METHOD__ );
 538+ return $success;
 539+ }
 540+
 541+ /**
 542+ * Represents a write lock on the messages key
 543+ *
 544+ * @return Boolean: success
 545+ */
 546+ function lock( $key ) {
 547+ $lockKey = $key . ':lock';
 548+ for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
 549+ sleep( 1 );
 550+ }
 551+
 552+ return $i >= MSG_WAIT_TIMEOUT;
 553+ }
 554+
 555+ function unlock( $key ) {
 556+ $lockKey = $key . ':lock';
 557+ $this->mMemc->delete( $lockKey );
 558+ }
 559+
 560+ /**
 561+ * Get a message from either the content language or the user language.
 562+ *
 563+ * @param $key String: the message cache key
 564+ * @param $useDB Boolean: get the message from the DB, false to use only
 565+ * the localisation
 566+ * @param $langcode String: code of the language to get the message for, if
 567+ * it is a valid code create a language for that language,
 568+ * if it is a string but not a valid code then make a basic
 569+ * language object, if it is a false boolean then use the
 570+ * current users language (as a fallback for the old
 571+ * parameter functionality), or if it is a true boolean
 572+ * then use the wikis content language (also as a
 573+ * fallback).
 574+ * @param $isFullKey Boolean: specifies whether $key is a two part key
 575+ * "msg/lang".
 576+ */
 577+ function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
 578+ global $wgLanguageCode, $wgContLang;
 579+
 580+ if ( !is_string( $key ) ) {
 581+ throw new MWException( 'Non-string key given' );
 582+ }
 583+
 584+ if ( strval( $key ) === '' ) {
 585+ # Shortcut: the empty key is always missing
 586+ return false;
 587+ }
 588+
 589+ $lang = wfGetLangObj( $langcode );
 590+ if ( !$lang ) {
 591+ throw new MWException( "Bad lang code $langcode given" );
 592+ }
 593+
 594+ $langcode = $lang->getCode();
 595+
 596+ $message = false;
 597+
 598+ # Normalise title-case input (with some inlining)
 599+ $lckey = str_replace( ' ', '_', $key );
 600+ if ( ord( $key ) < 128 ) {
 601+ $lckey[0] = strtolower( $lckey[0] );
 602+ $uckey = ucfirst( $lckey );
 603+ } else {
 604+ $lckey = $wgContLang->lcfirst( $lckey );
 605+ $uckey = $wgContLang->ucfirst( $lckey );
 606+ }
 607+
 608+ /**
 609+ * Record each message request, but only once per request.
 610+ * This information is not used unless $wgAdaptiveMessageCache
 611+ * is enabled.
 612+ */
 613+ $this->mRequestedMessages[$uckey] = true;
 614+
 615+ # Try the MediaWiki namespace
 616+ if( !$this->mDisable && $useDB ) {
 617+ $title = $uckey;
 618+ if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
 619+ $title .= '/' . $langcode;
 620+ }
 621+ $message = $this->getMsgFromNamespace( $title, $langcode );
 622+ }
 623+
 624+ # Try the array in the language object
 625+ if ( $message === false ) {
 626+ $message = $lang->getMessage( $lckey );
 627+ if ( is_null( $message ) ) {
 628+ $message = false;
 629+ }
 630+ }
 631+
 632+ # Try the array of another language
 633+ if( $message === false ) {
 634+ $parts = explode( '/', $lckey );
 635+ # We may get calls for things that are http-urls from sidebar
 636+ # Let's not load nonexistent languages for those
 637+ # They usually have more than one slash.
 638+ if ( count( $parts ) == 2 && $parts[1] !== '' ) {
 639+ $message = Language::getMessageFor( $parts[0], $parts[1] );
 640+ if ( is_null( $message ) ) {
 641+ $message = false;
 642+ }
 643+ }
 644+ }
 645+
 646+ # Is this a custom message? Try the default language in the db...
 647+ if( ( $message === false || $message === '-' ) &&
 648+ !$this->mDisable && $useDB &&
 649+ !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
 650+ $message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
 651+ }
 652+
 653+ # Final fallback
 654+ if( $message === false ) {
 655+ return false;
 656+ }
 657+
 658+ # Fix whitespace
 659+ $message = strtr( $message,
 660+ array(
 661+ # Fix for trailing whitespace, removed by textarea
 662+ '&#32;' => ' ',
 663+ # Fix for NBSP, converted to space by firefox
 664+ '&nbsp;' => "\xc2\xa0",
 665+ '&#160;' => "\xc2\xa0",
 666+ ) );
 667+
 668+ return $message;
 669+ }
 670+
 671+ /**
 672+ * Get a message from the MediaWiki namespace, with caching. The key must
 673+ * first be converted to two-part lang/msg form if necessary.
 674+ *
 675+ * @param $title String: Message cache key with initial uppercase letter.
 676+ * @param $code String: code denoting the language to try.
 677+ */
 678+ function getMsgFromNamespace( $title, $code ) {
 679+ global $wgAdaptiveMessageCache;
 680+
 681+ $this->load( $code );
 682+ if ( isset( $this->mCache[$code][$title] ) ) {
 683+ $entry = $this->mCache[$code][$title];
 684+ if ( substr( $entry, 0, 1 ) === ' ' ) {
 685+ return substr( $entry, 1 );
 686+ } elseif ( $entry === '!NONEXISTENT' ) {
 687+ return false;
 688+ } elseif( $entry === '!TOO BIG' ) {
 689+ // Fall through and try invididual message cache below
 690+ }
 691+ } else {
 692+ // XXX: This is not cached in process cache, should it?
 693+ $message = false;
 694+ wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
 695+ if ( $message !== false ) {
 696+ return $message;
 697+ }
 698+
 699+ /**
 700+ * If message cache is in normal mode, it is guaranteed
 701+ * (except bugs) that there is always entry (or placeholder)
 702+ * in the cache if message exists. Thus we can do minor
 703+ * performance improvement and return false early.
 704+ */
 705+ if ( !$wgAdaptiveMessageCache ) {
 706+ return false;
 707+ }
 708+ }
 709+
 710+ # Try the individual message cache
 711+ $titleKey = wfMemcKey( 'messages', 'individual', $title );
 712+ $entry = $this->mMemc->get( $titleKey );
 713+ if ( $entry ) {
 714+ if ( substr( $entry, 0, 1 ) === ' ' ) {
 715+ $this->mCache[$code][$title] = $entry;
 716+ return substr( $entry, 1 );
 717+ } elseif ( $entry === '!NONEXISTENT' ) {
 718+ $this->mCache[$code][$title] = '!NONEXISTENT';
 719+ return false;
 720+ } else {
 721+ # Corrupt/obsolete entry, delete it
 722+ $this->mMemc->delete( $titleKey );
 723+ }
 724+ }
 725+
 726+ # Try loading it from the database
 727+ $revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
 728+ if ( $revision ) {
 729+ $message = $revision->getText();
 730+ $this->mCache[$code][$title] = ' ' . $message;
 731+ $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
 732+ } else {
 733+ $message = false;
 734+ $this->mCache[$code][$title] = '!NONEXISTENT';
 735+ $this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
 736+ }
 737+
 738+ return $message;
 739+ }
 740+
 741+ /**
 742+ * @param $message string
 743+ * @param $interface bool
 744+ * @param $language
 745+ * @param $title Title
 746+ * @return string
 747+ */
 748+ function transform( $message, $interface = false, $language = null, $title = null ) {
 749+ // Avoid creating parser if nothing to transform
 750+ if( strpos( $message, '{{' ) === false ) {
 751+ return $message;
 752+ }
 753+
 754+ if ( $this->mInParser ) {
 755+ return $message;
 756+ }
 757+
 758+ $parser = $this->getParser();
 759+ if ( $parser ) {
 760+ $popts = $this->getParserOptions();
 761+ $popts->setInterfaceMessage( $interface );
 762+ $popts->setTargetLanguage( $language );
 763+ $popts->setUserLang( $language );
 764+
 765+ $this->mInParser = true;
 766+ $message = $parser->transformMsg( $message, $popts, $title );
 767+ $this->mInParser = false;
 768+ }
 769+ return $message;
 770+ }
 771+
 772+ /**
 773+ * @return Parser
 774+ */
 775+ function getParser() {
 776+ global $wgParser, $wgParserConf;
 777+ if ( !$this->mParser && isset( $wgParser ) ) {
 778+ # Do some initialisation so that we don't have to do it twice
 779+ $wgParser->firstCallInit();
 780+ # Clone it and store it
 781+ $class = $wgParserConf['class'];
 782+ if ( $class == 'Parser_DiffTest' ) {
 783+ # Uncloneable
 784+ $this->mParser = new $class( $wgParserConf );
 785+ } else {
 786+ $this->mParser = clone $wgParser;
 787+ }
 788+ }
 789+ return $this->mParser;
 790+ }
 791+
 792+ /**
 793+ * @param $text string
 794+ * @param $string Title|string
 795+ * @param $title Title
 796+ * @param $interface bool
 797+ * @param $linestart bool
 798+ * @param $language
 799+ * @return ParserOutput
 800+ */
 801+ public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null ) {
 802+ if ( $this->mInParser ) {
 803+ return htmlspecialchars( $text );
 804+ }
 805+
 806+ $parser = $this->getParser();
 807+ $popts = $this->getParserOptions();
 808+
 809+ if ( $interface ) {
 810+ $popts->setInterfaceMessage( true );
 811+ }
 812+ if ( $language !== null ) {
 813+ $popts->setTargetLanguage( $language );
 814+ }
 815+
 816+ if ( !$title || !$title instanceof Title ) {
 817+ global $wgTitle;
 818+ $title = $wgTitle;
 819+ }
 820+
 821+ $this->mInParser = true;
 822+ $res = $parser->parse( $text, $title, $popts, $linestart );
 823+ $this->mInParser = false;
 824+
 825+ return $res;
 826+ }
 827+
 828+ function disable() {
 829+ $this->mDisable = true;
 830+ }
 831+
 832+ function enable() {
 833+ $this->mDisable = false;
 834+ }
 835+
 836+ /**
 837+ * Clear all stored messages. Mainly used after a mass rebuild.
 838+ */
 839+ function clear() {
 840+ $langs = Language::getLanguageNames( false );
 841+ foreach ( array_keys($langs) as $code ) {
 842+ # Global cache
 843+ $this->mMemc->delete( wfMemcKey( 'messages', $code ) );
 844+ # Invalidate all local caches
 845+ $this->mMemc->delete( wfMemcKey( 'messages', $code, 'hash' ) );
 846+ }
 847+ $this->mLoadedLanguages = array();
 848+ }
 849+
 850+ public function figureMessage( $key ) {
 851+ global $wgLanguageCode;
 852+ $pieces = explode( '/', $key );
 853+ if( count( $pieces ) < 2 ) {
 854+ return array( $key, $wgLanguageCode );
 855+ }
 856+
 857+ $lang = array_pop( $pieces );
 858+ $validCodes = Language::getLanguageNames();
 859+ if( !array_key_exists( $lang, $validCodes ) ) {
 860+ return array( $key, $wgLanguageCode );
 861+ }
 862+
 863+ $message = implode( '/', $pieces );
 864+ return array( $message, $lang );
 865+ }
 866+
 867+ public static function logMessages() {
 868+ wfProfileIn( __METHOD__ );
 869+ global $wgAdaptiveMessageCache;
 870+ if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
 871+ wfProfileOut( __METHOD__ );
 872+ return;
 873+ }
 874+
 875+ $cachekey = wfMemckey( 'message-profiling' );
 876+ $cache = wfGetCache( CACHE_DB );
 877+ $data = $cache->get( $cachekey );
 878+
 879+ if ( !$data ) {
 880+ $data = array();
 881+ }
 882+
 883+ $age = self::$mAdaptiveDataAge;
 884+ $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
 885+ foreach ( array_keys( $data ) as $key ) {
 886+ if ( $key < $filterDate ) {
 887+ unset( $data[$key] );
 888+ }
 889+ }
 890+
 891+ $index = substr( wfTimestampNow(), 0, 8 );
 892+ if ( !isset( $data[$index] ) ) {
 893+ $data[$index] = array();
 894+ }
 895+
 896+ foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
 897+ if ( !isset( $data[$index][$message] ) ) {
 898+ $data[$index][$message] = 0;
 899+ }
 900+ $data[$index][$message]++;
 901+ }
 902+
 903+ $cache->set( $cachekey, $data );
 904+ wfProfileOut( __METHOD__ );
 905+ }
 906+
 907+ public function getMostUsedMessages() {
 908+ wfProfileIn( __METHOD__ );
 909+ $cachekey = wfMemcKey( 'message-profiling' );
 910+ $cache = wfGetCache( CACHE_DB );
 911+ $data = $cache->get( $cachekey );
 912+ if ( !$data ) {
 913+ wfProfileOut( __METHOD__ );
 914+ return array();
 915+ }
 916+
 917+ $list = array();
 918+
 919+ foreach( $data as $messages ) {
 920+ foreach( $messages as $message => $count ) {
 921+ $key = $message;
 922+ if ( !isset( $list[$key] ) ) {
 923+ $list[$key] = 0;
 924+ }
 925+ $list[$key] += $count;
 926+ }
 927+ }
 928+
 929+ $max = max( $list );
 930+ foreach ( $list as $message => $count ) {
 931+ if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
 932+ unset( $list[$message] );
 933+ }
 934+ }
 935+
 936+ wfProfileOut( __METHOD__ );
 937+ return array_keys( $list );
 938+ }
 939+
 940+}
Property changes on: trunk/phase3/includes/cache/MessageCache.php
___________________________________________________________________
Added: svn:eol-style
1941 + native
Added: svn:keywords
2942 + Author Date Id Revision
Index: trunk/phase3/includes/cache/SquidUpdate.php
@@ -0,0 +1,203 @@
 2+<?php
 3+/**
 4+ * See deferred.txt
 5+ * @file
 6+ * @ingroup Cache
 7+ */
 8+
 9+/**
 10+ * Handles purging appropriate Squid URLs given a title (or titles)
 11+ * @ingroup Cache
 12+ */
 13+class SquidUpdate {
 14+ var $urlArr, $mMaxTitles;
 15+
 16+ function __construct( $urlArr = Array(), $maxTitles = false ) {
 17+ global $wgMaxSquidPurgeTitles;
 18+ if ( $maxTitles === false ) {
 19+ $this->mMaxTitles = $wgMaxSquidPurgeTitles;
 20+ } else {
 21+ $this->mMaxTitles = $maxTitles;
 22+ }
 23+ if ( count( $urlArr ) > $this->mMaxTitles ) {
 24+ $urlArr = array_slice( $urlArr, 0, $this->mMaxTitles );
 25+ }
 26+ $this->urlArr = $urlArr;
 27+ }
 28+
 29+ static function newFromLinksTo( &$title ) {
 30+ global $wgMaxSquidPurgeTitles;
 31+ wfProfileIn( __METHOD__ );
 32+
 33+ # Get a list of URLs linking to this page
 34+ $dbr = wfGetDB( DB_SLAVE );
 35+ $res = $dbr->select( array( 'links', 'page' ),
 36+ array( 'page_namespace', 'page_title' ),
 37+ array(
 38+ 'pl_namespace' => $title->getNamespace(),
 39+ 'pl_title' => $title->getDBkey(),
 40+ 'pl_from=page_id' ),
 41+ __METHOD__ );
 42+ $blurlArr = $title->getSquidURLs();
 43+ if ( $dbr->numRows( $res ) <= $wgMaxSquidPurgeTitles ) {
 44+ foreach ( $res as $BL ) {
 45+ $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title ) ;
 46+ $blurlArr[] = $tobj->getInternalURL();
 47+ }
 48+ }
 49+
 50+ wfProfileOut( __METHOD__ );
 51+ return new SquidUpdate( $blurlArr );
 52+ }
 53+
 54+ /**
 55+ * Create a SquidUpdate from an array of Title objects, or a TitleArray object
 56+ */
 57+ static function newFromTitles( $titles, $urlArr = array() ) {
 58+ global $wgMaxSquidPurgeTitles;
 59+ $i = 0;
 60+ foreach ( $titles as $title ) {
 61+ $urlArr[] = $title->getInternalURL();
 62+ if ( $i++ > $wgMaxSquidPurgeTitles ) {
 63+ break;
 64+ }
 65+ }
 66+ return new SquidUpdate( $urlArr );
 67+ }
 68+
 69+ static function newSimplePurge( &$title ) {
 70+ $urlArr = $title->getSquidURLs();
 71+ return new SquidUpdate( $urlArr );
 72+ }
 73+
 74+ function doUpdate() {
 75+ SquidUpdate::purge( $this->urlArr );
 76+ }
 77+
 78+ /* Purges a list of Squids defined in $wgSquidServers.
 79+ $urlArr should contain the full URLs to purge as values
 80+ (example: $urlArr[] = 'http://my.host/something')
 81+ XXX report broken Squids per mail or log */
 82+
 83+ static function purge( $urlArr ) {
 84+ global $wgSquidServers, $wgHTCPMulticastAddress, $wgHTCPPort;
 85+
 86+ /*if ( (@$wgSquidServers[0]) == 'echo' ) {
 87+ echo implode("<br />\n", $urlArr) . "<br />\n";
 88+ return;
 89+ }*/
 90+
 91+ if( !$urlArr ) {
 92+ return;
 93+ }
 94+
 95+ if ( $wgHTCPMulticastAddress && $wgHTCPPort ) {
 96+ return SquidUpdate::HTCPPurge( $urlArr );
 97+ }
 98+
 99+ wfProfileIn( __METHOD__ );
 100+
 101+ $maxSocketsPerSquid = 8; // socket cap per Squid
 102+ $urlsPerSocket = 400; // 400 seems to be a good tradeoff, opening a socket takes a while
 103+ $socketsPerSquid = ceil( count( $urlArr ) / $urlsPerSocket );
 104+ if ( $socketsPerSquid > $maxSocketsPerSquid ) {
 105+ $socketsPerSquid = $maxSocketsPerSquid;
 106+ }
 107+
 108+ $pool = new SquidPurgeClientPool;
 109+ $chunks = array_chunk( $urlArr, ceil( count( $urlArr ) / $socketsPerSquid ) );
 110+ foreach ( $wgSquidServers as $server ) {
 111+ foreach ( $chunks as $chunk ) {
 112+ $client = new SquidPurgeClient( $server );
 113+ foreach ( $chunk as $url ) {
 114+ $client->queuePurge( $url );
 115+ }
 116+ $pool->addClient( $client );
 117+ }
 118+ }
 119+ $pool->run();
 120+
 121+ wfProfileOut( __METHOD__ );
 122+ }
 123+
 124+ static function HTCPPurge( $urlArr ) {
 125+ global $wgHTCPMulticastAddress, $wgHTCPMulticastTTL, $wgHTCPPort;
 126+ wfProfileIn( __METHOD__ );
 127+
 128+ $htcpOpCLR = 4; // HTCP CLR
 129+
 130+ // FIXME PHP doesn't support these socket constants (include/linux/in.h)
 131+ if( !defined( "IPPROTO_IP" ) ) {
 132+ define( "IPPROTO_IP", 0 );
 133+ define( "IP_MULTICAST_LOOP", 34 );
 134+ define( "IP_MULTICAST_TTL", 33 );
 135+ }
 136+
 137+ // pfsockopen doesn't work because we need set_sock_opt
 138+ $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
 139+ if ( $conn ) {
 140+ // Set socket options
 141+ socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
 142+ if ( $wgHTCPMulticastTTL != 1 )
 143+ socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
 144+ $wgHTCPMulticastTTL );
 145+
 146+ foreach ( $urlArr as $url ) {
 147+ if( !is_string( $url ) ) {
 148+ throw new MWException( 'Bad purge URL' );
 149+ }
 150+ $url = SquidUpdate::expand( $url );
 151+
 152+ // Construct a minimal HTCP request diagram
 153+ // as per RFC 2756
 154+ // Opcode 'CLR', no response desired, no auth
 155+ $htcpTransID = rand();
 156+
 157+ $htcpSpecifier = pack( 'na4na*na8n',
 158+ 4, 'HEAD', strlen( $url ), $url,
 159+ 8, 'HTTP/1.0', 0 );
 160+
 161+ $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
 162+ $htcpLen = 4 + $htcpDataLen + 2;
 163+
 164+ // Note! Squid gets the bit order of the first
 165+ // word wrong, wrt the RFC. Apparently no other
 166+ // implementation exists, so adapt to Squid
 167+ $htcpPacket = pack( 'nxxnCxNxxa*n',
 168+ $htcpLen, $htcpDataLen, $htcpOpCLR,
 169+ $htcpTransID, $htcpSpecifier, 2);
 170+
 171+ // Send out
 172+ wfDebug( "Purging URL $url via HTCP\n" );
 173+ socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
 174+ $wgHTCPMulticastAddress, $wgHTCPPort );
 175+ }
 176+ } else {
 177+ $errstr = socket_strerror( socket_last_error() );
 178+ wfDebug( __METHOD__ . "(): Error opening UDP socket: $errstr\n" );
 179+ }
 180+ wfProfileOut( __METHOD__ );
 181+ }
 182+
 183+ /**
 184+ * Expand local URLs to fully-qualified URLs using the internal protocol
 185+ * and host defined in $wgInternalServer. Input that's already fully-
 186+ * qualified will be passed through unchanged.
 187+ *
 188+ * This is used to generate purge URLs that may be either local to the
 189+ * main wiki or include a non-native host, such as images hosted on a
 190+ * second internal server.
 191+ *
 192+ * Client functions should not need to call this.
 193+ *
 194+ * @return string
 195+ */
 196+ static function expand( $url ) {
 197+ global $wgInternalServer, $wgServer;
 198+ $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
 199+ if( $url !== '' && $url[0] == '/' ) {
 200+ return $server . $url;
 201+ }
 202+ return $url;
 203+ }
 204+}
Property changes on: trunk/phase3/includes/cache/SquidUpdate.php
___________________________________________________________________
Added: svn:eol-style
1205 + native
Added: svn:keywords
2206 + Author Date Id Revision

Follow-up revisions

RevisionCommit summaryAuthorDate
r86906Followup r86905, after saving GlobalFunctions.phpreedy21:41, 25 April 2011

Status & tagging log