r86903 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86902‎ | r86903 | r86904 >
Date:21:33, 25 April 2011
Author:reedy
Status:ok (Comments)
Tags:
Comment:
Move 3 files into cache directory

Move in AutoLoader to cache section also
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/CacheDependency.php (deleted) (history)
  • /trunk/phase3/includes/HTMLCacheUpdate.php (deleted) (history)
  • /trunk/phase3/includes/HTMLFileCache.php (deleted) (history)
  • /trunk/phase3/includes/cache/CacheDependency.php (added) (history)
  • /trunk/phase3/includes/cache/HTMLCacheUpdate.php (added) (history)
  • /trunk/phase3/includes/cache/HTMLFileCache.php (added) (history)

Diff [purge]

Index: trunk/phase3/includes/CacheDependency.php
@@ -1,367 +0,0 @@
2 -<?php
3 -/**
4 - * This class stores an arbitrary value along with its dependencies.
5 - * Users should typically only use DependencyWrapper::getValueFromCache(),
6 - * rather than instantiating one of these objects directly.
7 - * @ingroup Cache
8 - */
9 -
10 -class DependencyWrapper {
11 - var $value;
12 - var $deps;
13 -
14 - /**
15 - * Create an instance.
16 - * @param $value Mixed: the user-supplied value
17 - * @param $deps Mixed: a dependency or dependency array. All dependencies
18 - * must be objects implementing CacheDependency.
19 - */
20 - function __construct( $value = false, $deps = array() ) {
21 - $this->value = $value;
22 -
23 - if ( !is_array( $deps ) ) {
24 - $deps = array( $deps );
25 - }
26 -
27 - $this->deps = $deps;
28 - }
29 -
30 - /**
31 - * Returns true if any of the dependencies have expired
32 - */
33 - function isExpired() {
34 - foreach ( $this->deps as $dep ) {
35 - if ( $dep->isExpired() ) {
36 - return true;
37 - }
38 - }
39 -
40 - return false;
41 - }
42 -
43 - /**
44 - * Initialise dependency values in preparation for storing. This must be
45 - * called before serialization.
46 - */
47 - function initialiseDeps() {
48 - foreach ( $this->deps as $dep ) {
49 - $dep->loadDependencyValues();
50 - }
51 - }
52 -
53 - /**
54 - * Get the user-defined value
55 - */
56 - function getValue() {
57 - return $this->value;
58 - }
59 -
60 - /**
61 - * Store the wrapper to a cache
62 - */
63 - function storeToCache( $cache, $key, $expiry = 0 ) {
64 - $this->initialiseDeps();
65 - $cache->set( $key, $this, $expiry );
66 - }
67 -
68 - /**
69 - * Attempt to get a value from the cache. If the value is expired or missing,
70 - * it will be generated with the callback function (if present), and the newly
71 - * calculated value will be stored to the cache in a wrapper.
72 - *
73 - * @param $cache Object: a cache object such as $wgMemc
74 - * @param $key String: the cache key
75 - * @param $expiry Integer: the expiry timestamp or interval in seconds
76 - * @param $callback Mixed: the callback for generating the value, or false
77 - * @param $callbackParams Array: the function parameters for the callback
78 - * @param $deps Array: the dependencies to store on a cache miss. Note: these
79 - * are not the dependencies used on a cache hit! Cache hits use the stored
80 - * dependency array.
81 - *
82 - * @return mixed The value, or null if it was not present in the cache and no
83 - * callback was defined.
84 - */
85 - static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false,
86 - $callbackParams = array(), $deps = array() )
87 - {
88 - $obj = $cache->get( $key );
89 -
90 - if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
91 - $value = $obj->value;
92 - } elseif ( $callback ) {
93 - $value = call_user_func_array( $callback, $callbackParams );
94 - # Cache the newly-generated value
95 - $wrapper = new DependencyWrapper( $value, $deps );
96 - $wrapper->storeToCache( $cache, $key, $expiry );
97 - } else {
98 - $value = null;
99 - }
100 -
101 - return $value;
102 - }
103 -}
104 -
105 -/**
106 - * @ingroup Cache
107 - */
108 -abstract class CacheDependency {
109 - /**
110 - * Returns true if the dependency is expired, false otherwise
111 - */
112 - abstract function isExpired();
113 -
114 - /**
115 - * Hook to perform any expensive pre-serialize loading of dependency values.
116 - */
117 - function loadDependencyValues() { }
118 -}
119 -
120 -/**
121 - * @ingroup Cache
122 - */
123 -class FileDependency extends CacheDependency {
124 - var $filename, $timestamp;
125 -
126 - /**
127 - * Create a file dependency
128 - *
129 - * @param $filename String: the name of the file, preferably fully qualified
130 - * @param $timestamp Mixed: the unix last modified timestamp, or false if the
131 - * file does not exist. If omitted, the timestamp will be loaded from
132 - * the file.
133 - *
134 - * A dependency on a nonexistent file will be triggered when the file is
135 - * created. A dependency on an existing file will be triggered when the
136 - * file is changed.
137 - */
138 - function __construct( $filename, $timestamp = null ) {
139 - $this->filename = $filename;
140 - $this->timestamp = $timestamp;
141 - }
142 -
143 - function __sleep() {
144 - $this->loadDependencyValues();
145 - return array( 'filename', 'timestamp' );
146 - }
147 -
148 - function loadDependencyValues() {
149 - if ( is_null( $this->timestamp ) ) {
150 - if ( !file_exists( $this->filename ) ) {
151 - # Dependency on a non-existent file
152 - # This is a valid concept!
153 - $this->timestamp = false;
154 - } else {
155 - $this->timestamp = filemtime( $this->filename );
156 - }
157 - }
158 - }
159 -
160 - function isExpired() {
161 - if ( !file_exists( $this->filename ) ) {
162 - if ( $this->timestamp === false ) {
163 - # Still nonexistent
164 - return false;
165 - } else {
166 - # Deleted
167 - wfDebug( "Dependency triggered: {$this->filename} deleted.\n" );
168 - return true;
169 - }
170 - } else {
171 - $lastmod = filemtime( $this->filename );
172 - if ( $lastmod > $this->timestamp ) {
173 - # Modified or created
174 - wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
175 - return true;
176 - } else {
177 - # Not modified
178 - return false;
179 - }
180 - }
181 - }
182 -}
183 -
184 -/**
185 - * @ingroup Cache
186 - */
187 -class TitleDependency extends CacheDependency {
188 - var $titleObj;
189 - var $ns, $dbk;
190 - var $touched;
191 -
192 - /**
193 - * Construct a title dependency
194 - * @param $title Title
195 - */
196 - function __construct( Title $title ) {
197 - $this->titleObj = $title;
198 - $this->ns = $title->getNamespace();
199 - $this->dbk = $title->getDBkey();
200 - }
201 -
202 - function loadDependencyValues() {
203 - $this->touched = $this->getTitle()->getTouched();
204 - }
205 -
206 - /**
207 - * Get rid of bulky Title object for sleep
208 - */
209 - function __sleep() {
210 - return array( 'ns', 'dbk', 'touched' );
211 - }
212 -
213 - function getTitle() {
214 - if ( !isset( $this->titleObj ) ) {
215 - $this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
216 - }
217 -
218 - return $this->titleObj;
219 - }
220 -
221 - function isExpired() {
222 - $touched = $this->getTitle()->getTouched();
223 -
224 - if ( $this->touched === false ) {
225 - if ( $touched === false ) {
226 - # Still missing
227 - return false;
228 - } else {
229 - # Created
230 - return true;
231 - }
232 - } elseif ( $touched === false ) {
233 - # Deleted
234 - return true;
235 - } elseif ( $touched > $this->touched ) {
236 - # Updated
237 - return true;
238 - } else {
239 - # Unmodified
240 - return false;
241 - }
242 - }
243 -}
244 -
245 -/**
246 - * @ingroup Cache
247 - */
248 -class TitleListDependency extends CacheDependency {
249 - var $linkBatch;
250 - var $timestamps;
251 -
252 - /**
253 - * Construct a dependency on a list of titles
254 - */
255 - function __construct( LinkBatch $linkBatch ) {
256 - $this->linkBatch = $linkBatch;
257 - }
258 -
259 - function calculateTimestamps() {
260 - # Initialise values to false
261 - $timestamps = array();
262 -
263 - foreach ( $this->getLinkBatch()->data as $ns => $dbks ) {
264 - if ( count( $dbks ) > 0 ) {
265 - $timestamps[$ns] = array();
266 -
267 - foreach ( $dbks as $dbk => $value ) {
268 - $timestamps[$ns][$dbk] = false;
269 - }
270 - }
271 - }
272 -
273 - # Do the query
274 - if ( count( $timestamps ) ) {
275 - $dbr = wfGetDB( DB_SLAVE );
276 - $where = $this->getLinkBatch()->constructSet( 'page', $dbr );
277 - $res = $dbr->select(
278 - 'page',
279 - array( 'page_namespace', 'page_title', 'page_touched' ),
280 - $where,
281 - __METHOD__
282 - );
283 -
284 - foreach ( $res as $row ) {
285 - $timestamps[$row->page_namespace][$row->page_title] = $row->page_touched;
286 - }
287 - }
288 -
289 - return $timestamps;
290 - }
291 -
292 - function loadDependencyValues() {
293 - $this->timestamps = $this->calculateTimestamps();
294 - }
295 -
296 - function __sleep() {
297 - return array( 'timestamps' );
298 - }
299 -
300 - function getLinkBatch() {
301 - if ( !isset( $this->linkBatch ) ) {
302 - $this->linkBatch = new LinkBatch;
303 - $this->linkBatch->setArray( $this->timestamps );
304 - }
305 - return $this->linkBatch;
306 - }
307 -
308 - function isExpired() {
309 - $newTimestamps = $this->calculateTimestamps();
310 -
311 - foreach ( $this->timestamps as $ns => $dbks ) {
312 - foreach ( $dbks as $dbk => $oldTimestamp ) {
313 - $newTimestamp = $newTimestamps[$ns][$dbk];
314 -
315 - if ( $oldTimestamp === false ) {
316 - if ( $newTimestamp === false ) {
317 - # Still missing
318 - } else {
319 - # Created
320 - return true;
321 - }
322 - } elseif ( $newTimestamp === false ) {
323 - # Deleted
324 - return true;
325 - } elseif ( $newTimestamp > $oldTimestamp ) {
326 - # Updated
327 - return true;
328 - } else {
329 - # Unmodified
330 - }
331 - }
332 - }
333 -
334 - return false;
335 - }
336 -}
337 -
338 -/**
339 - * @ingroup Cache
340 - */
341 -class GlobalDependency extends CacheDependency {
342 - var $name, $value;
343 -
344 - function __construct( $name ) {
345 - $this->name = $name;
346 - $this->value = $GLOBALS[$name];
347 - }
348 -
349 - function isExpired() {
350 - return $GLOBALS[$this->name] != $this->value;
351 - }
352 -}
353 -
354 -/**
355 - * @ingroup Cache
356 - */
357 -class ConstantDependency extends CacheDependency {
358 - var $name, $value;
359 -
360 - function __construct( $name ) {
361 - $this->name = $name;
362 - $this->value = constant( $name );
363 - }
364 -
365 - function isExpired() {
366 - return constant( $this->name ) != $this->value;
367 - }
368 -}
Index: trunk/phase3/includes/HTMLFileCache.php
@@ -1,250 +0,0 @@
2 -<?php
3 -/**
4 - * Contain the HTMLFileCache class
5 - * @file
6 - * @ingroup Cache
7 - */
8 -
9 -/**
10 - * Handles talking to the file cache, putting stuff in and taking it back out.
11 - * Mostly called from Article.php for the emergency abort/fallback to cache.
12 - *
13 - * Global options that affect this module:
14 - * - $wgCachePages
15 - * - $wgCacheEpoch
16 - * - $wgUseFileCache
17 - * - $wgCacheDirectory
18 - * - $wgFileCacheDirectory
19 - * - $wgUseGzip
20 - *
21 - * @ingroup Cache
22 - */
23 -class HTMLFileCache {
24 -
25 - /**
26 - * @var Title
27 - */
28 - var $mTitle;
29 - var $mFileCache, $mType;
30 -
31 - public function __construct( &$title, $type = 'view' ) {
32 - $this->mTitle = $title;
33 - $this->mType = ($type == 'raw' || $type == 'view' ) ? $type : false;
34 - $this->fileCacheName(); // init name
35 - }
36 -
37 - public function fileCacheName() {
38 - if( !$this->mFileCache ) {
39 - global $wgCacheDirectory, $wgFileCacheDirectory, $wgFileCacheDepth;
40 -
41 - if ( $wgFileCacheDirectory ) {
42 - $dir = $wgFileCacheDirectory;
43 - } elseif ( $wgCacheDirectory ) {
44 - $dir = "$wgCacheDirectory/html";
45 - } else {
46 - throw new MWException( 'Please set $wgCacheDirectory in LocalSettings.php if you wish to use the HTML file cache' );
47 - }
48 -
49 - # Store raw pages (like CSS hits) elsewhere
50 - $subdir = ($this->mType === 'raw') ? 'raw/' : '';
51 -
52 - $key = $this->mTitle->getPrefixedDbkey();
53 - if ( $wgFileCacheDepth > 0 ) {
54 - $hash = md5( $key );
55 - for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
56 - $subdir .= substr( $hash, 0, $i ) . '/';
57 - }
58 - }
59 - # Avoid extension confusion
60 - $key = str_replace( '.', '%2E', urlencode( $key ) );
61 - $this->mFileCache = "{$dir}/{$subdir}{$key}.html";
62 -
63 - if( $this->useGzip() ) {
64 - $this->mFileCache .= '.gz';
65 - }
66 -
67 - wfDebug( __METHOD__ . ": {$this->mFileCache}\n" );
68 - }
69 - return $this->mFileCache;
70 - }
71 -
72 - public function isFileCached() {
73 - if( $this->mType === false ) {
74 - return false;
75 - }
76 - return file_exists( $this->fileCacheName() );
77 - }
78 -
79 - public function fileCacheTime() {
80 - return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) );
81 - }
82 -
83 - /**
84 - * Check if pages can be cached for this request/user
85 - * @return bool
86 - */
87 - public static function useFileCache() {
88 - global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang;
89 - if( !$wgUseFileCache ) {
90 - return false;
91 - }
92 - // Get all query values
93 - $queryVals = $wgRequest->getValues();
94 - foreach( $queryVals as $query => $val ) {
95 - if( $query == 'title' || $query == 'curid' ) {
96 - continue;
97 - }
98 - // Normal page view in query form can have action=view.
99 - // Raw hits for pages also stored, like .css pages for example.
100 - else if( $query == 'action' && ($val == 'view' || $val == 'raw') ) {
101 - continue;
102 - } else if( $query == 'usemsgcache' && $val == 'yes' ) {
103 - continue;
104 - }
105 - // Below are header setting params
106 - else if( $query == 'maxage' || $query == 'smaxage' || $query == 'ctype' || $query == 'gen' ) {
107 - continue;
108 - } else {
109 - return false;
110 - }
111 - }
112 - // Check for non-standard user language; this covers uselang,
113 - // and extensions for auto-detecting user language.
114 - $ulang = $wgLang->getCode();
115 - $clang = $wgContLang->getCode();
116 - // Check that there are no other sources of variation
117 - return !$wgShowIPinHeader && !$wgUser->getId() && !$wgUser->getNewtalk() && $ulang == $clang;
118 - }
119 -
120 - /*
121 - * Check if up to date cache file exists
122 - * @param $timestamp string
123 - */
124 - public function isFileCacheGood( $timestamp = '' ) {
125 - global $wgCacheEpoch;
126 -
127 - if( !$this->isFileCached() ) {
128 - return false;
129 - }
130 -
131 - $cachetime = $this->fileCacheTime();
132 - $good = $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime;
133 -
134 - wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n");
135 - return $good;
136 - }
137 -
138 - public function useGzip() {
139 - global $wgUseGzip;
140 - return $wgUseGzip;
141 - }
142 -
143 - /* In handy string packages */
144 - public function fetchRawText() {
145 - return file_get_contents( $this->fileCacheName() );
146 - }
147 -
148 - public function fetchPageText() {
149 - if( $this->useGzip() ) {
150 - /* Why is there no gzfile_get_contents() or gzdecode()? */
151 - return implode( '', gzfile( $this->fileCacheName() ) );
152 - } else {
153 - return $this->fetchRawText();
154 - }
155 - }
156 -
157 - /* Working directory to/from output */
158 - public function loadFromFileCache() {
159 - global $wgOut, $wgMimeType, $wgOutputEncoding, $wgLanguageCode;
160 - wfDebug( __METHOD__ . "()\n");
161 - $filename = $this->fileCacheName();
162 - // Raw pages should handle cache control on their own,
163 - // even when using file cache. This reduces hits from clients.
164 - if( $this->mType !== 'raw' ) {
165 - $wgOut->sendCacheControl();
166 - header( "Content-Type: $wgMimeType; charset={$wgOutputEncoding}" );
167 - header( "Content-Language: $wgLanguageCode" );
168 - }
169 -
170 - if( $this->useGzip() ) {
171 - if( wfClientAcceptsGzip() ) {
172 - header( 'Content-Encoding: gzip' );
173 - } else {
174 - /* Send uncompressed */
175 - readgzfile( $filename );
176 - return;
177 - }
178 - }
179 - readfile( $filename );
180 - $wgOut->disable(); // tell $wgOut that output is taken care of
181 - }
182 -
183 - protected function checkCacheDirs() {
184 - $filename = $this->fileCacheName();
185 - $mydir2 = substr($filename,0,strrpos($filename,'/')); # subdirectory level 2
186 - $mydir1 = substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1
187 -
188 - wfMkdirParents( $mydir1 );
189 - wfMkdirParents( $mydir2 );
190 - }
191 -
192 - public function saveToFileCache( $text ) {
193 - global $wgUseFileCache;
194 - if( !$wgUseFileCache || strlen( $text ) < 512 ) {
195 - // Disabled or empty/broken output (OOM and PHP errors)
196 - return $text;
197 - }
198 -
199 - wfDebug( __METHOD__ . "()\n", false);
200 -
201 - $this->checkCacheDirs();
202 -
203 - $f = fopen( $this->fileCacheName(), 'w' );
204 - if($f) {
205 - $now = wfTimestampNow();
206 - if( $this->useGzip() ) {
207 - $rawtext = str_replace( '</html>',
208 - '<!-- Cached/compressed '.$now." -->\n</html>",
209 - $text );
210 - $text = gzencode( $rawtext );
211 - } else {
212 - $text = str_replace( '</html>',
213 - '<!-- Cached '.$now." -->\n</html>",
214 - $text );
215 - }
216 - fwrite( $f, $text );
217 - fclose( $f );
218 - if( $this->useGzip() ) {
219 - if( wfClientAcceptsGzip() ) {
220 - header( 'Content-Encoding: gzip' );
221 - return $text;
222 - } else {
223 - return $rawtext;
224 - }
225 - } else {
226 - return $text;
227 - }
228 - }
229 - return $text;
230 - }
231 -
232 - public static function clearFileCache( $title ) {
233 - global $wgUseFileCache;
234 -
235 - if ( !$wgUseFileCache ) {
236 - return false;
237 - }
238 -
239 - wfSuppressWarnings();
240 -
241 - $fc = new self( $title, 'view' );
242 - unlink( $fc->fileCacheName() );
243 -
244 - $fc = new self( $title, 'raw' );
245 - unlink( $fc->fileCacheName() );
246 -
247 - wfRestoreWarnings();
248 -
249 - return true;
250 - }
251 -}
Index: trunk/phase3/includes/HTMLCacheUpdate.php
@@ -1,237 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Class to invalidate the HTML cache of all the pages linking to a given title.
6 - * Small numbers of links will be done immediately, large numbers are pushed onto
7 - * the job queue.
8 - *
9 - * This class is designed to work efficiently with small numbers of links, and
10 - * to work reasonably well with up to ~10^5 links. Above ~10^6 links, the memory
11 - * and time requirements of loading all backlinked IDs in doUpdate() might become
12 - * prohibitive. The requirements measured at Wikimedia are approximately:
13 - *
14 - * memory: 48 bytes per row
15 - * time: 16us per row for the query plus processing
16 - *
17 - * The reason this query is done is to support partitioning of the job
18 - * by backlinked ID. The memory issue could be allieviated by doing this query in
19 - * batches, but of course LIMIT with an offset is inefficient on the DB side.
20 - *
21 - * The class is nevertheless a vast improvement on the previous method of using
22 - * File::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
23 - * link.
24 - *
25 - * @ingroup Cache
26 - */
27 -class HTMLCacheUpdate
28 -{
29 - /**
30 - * @var Title
31 - */
32 - public $mTitle;
33 -
34 - public $mTable, $mPrefix, $mStart, $mEnd;
35 - public $mRowsPerJob, $mRowsPerQuery;
36 -
37 - function __construct( $titleTo, $table, $start = false, $end = false ) {
38 - global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
39 -
40 - $this->mTitle = $titleTo;
41 - $this->mTable = $table;
42 - $this->mStart = $start;
43 - $this->mEnd = $end;
44 - $this->mRowsPerJob = $wgUpdateRowsPerJob;
45 - $this->mRowsPerQuery = $wgUpdateRowsPerQuery;
46 - $this->mCache = $this->mTitle->getBacklinkCache();
47 - }
48 -
49 - public function doUpdate() {
50 - if ( $this->mStart || $this->mEnd ) {
51 - $this->doPartialUpdate();
52 - return;
53 - }
54 -
55 - # Get an estimate of the number of rows from the BacklinkCache
56 - $numRows = $this->mCache->getNumLinks( $this->mTable );
57 - if ( $numRows > $this->mRowsPerJob * 2 ) {
58 - # Do fast cached partition
59 - $this->insertJobs();
60 - } else {
61 - # Get the links from the DB
62 - $titleArray = $this->mCache->getLinks( $this->mTable );
63 - # Check if the row count estimate was correct
64 - if ( $titleArray->count() > $this->mRowsPerJob * 2 ) {
65 - # Not correct, do accurate partition
66 - wfDebug( __METHOD__.": row count estimate was incorrect, repartitioning\n" );
67 - $this->insertJobsFromTitles( $titleArray );
68 - } else {
69 - $this->invalidateTitles( $titleArray );
70 - }
71 - }
72 - }
73 -
74 - /**
75 - * Update some of the backlinks, defined by a page ID range
76 - */
77 - protected function doPartialUpdate() {
78 - $titleArray = $this->mCache->getLinks( $this->mTable, $this->mStart, $this->mEnd );
79 - if ( $titleArray->count() <= $this->mRowsPerJob * 2 ) {
80 - # This partition is small enough, do the update
81 - $this->invalidateTitles( $titleArray );
82 - } else {
83 - # Partitioning was excessively inaccurate. Divide the job further.
84 - # This can occur when a large number of links are added in a short
85 - # period of time, say by updating a heavily-used template.
86 - $this->insertJobsFromTitles( $titleArray );
87 - }
88 - }
89 -
90 - /**
91 - * Partition the current range given by $this->mStart and $this->mEnd,
92 - * using a pre-calculated title array which gives the links in that range.
93 - * Queue the resulting jobs.
94 - */
95 - protected function insertJobsFromTitles( $titleArray ) {
96 - # We make subpartitions in the sense that the start of the first job
97 - # will be the start of the parent partition, and the end of the last
98 - # job will be the end of the parent partition.
99 - $jobs = array();
100 - $start = $this->mStart; # start of the current job
101 - $numTitles = 0;
102 - foreach ( $titleArray as $title ) {
103 - $id = $title->getArticleID();
104 - # $numTitles is now the number of titles in the current job not
105 - # including the current ID
106 - if ( $numTitles >= $this->mRowsPerJob ) {
107 - # Add a job up to but not including the current ID
108 - $params = array(
109 - 'table' => $this->mTable,
110 - 'start' => $start,
111 - 'end' => $id - 1
112 - );
113 - $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
114 - $start = $id;
115 - $numTitles = 0;
116 - }
117 - $numTitles++;
118 - }
119 - # Last job
120 - $params = array(
121 - 'table' => $this->mTable,
122 - 'start' => $start,
123 - 'end' => $this->mEnd
124 - );
125 - $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
126 - wfDebug( __METHOD__.": repartitioning into " . count( $jobs ) . " jobs\n" );
127 -
128 - if ( count( $jobs ) < 2 ) {
129 - # I don't think this is possible at present, but handling this case
130 - # makes the code a bit more robust against future code updates and
131 - # avoids a potential infinite loop of repartitioning
132 - wfDebug( __METHOD__.": repartitioning failed!\n" );
133 - $this->invalidateTitles( $titleArray );
134 - return;
135 - }
136 -
137 - Job::batchInsert( $jobs );
138 - }
139 -
140 - protected function insertJobs() {
141 - $batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob );
142 - if ( !$batches ) {
143 - return;
144 - }
145 - $jobs = array();
146 - foreach ( $batches as $batch ) {
147 - $params = array(
148 - 'table' => $this->mTable,
149 - 'start' => $batch[0],
150 - 'end' => $batch[1],
151 - );
152 - $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
153 - }
154 - Job::batchInsert( $jobs );
155 - }
156 -
157 - /**
158 - * Invalidate a range of pages, right now
159 - * @deprecated
160 - */
161 - public function invalidate( $startId = false, $endId = false ) {
162 - $titleArray = $this->mCache->getLinks( $this->mTable, $startId, $endId );
163 - $this->invalidateTitles( $titleArray );
164 - }
165 -
166 - /**
167 - * Invalidate an array (or iterator) of Title objects, right now
168 - */
169 - protected function invalidateTitles( $titleArray ) {
170 - global $wgUseFileCache, $wgUseSquid;
171 -
172 - $dbw = wfGetDB( DB_MASTER );
173 - $timestamp = $dbw->timestamp();
174 -
175 - # Get all IDs in this query into an array
176 - $ids = array();
177 - foreach ( $titleArray as $title ) {
178 - $ids[] = $title->getArticleID();
179 - }
180 -
181 - if ( !$ids ) {
182 - return;
183 - }
184 -
185 - # Update page_touched
186 - $batches = array_chunk( $ids, $this->mRowsPerQuery );
187 - foreach ( $batches as $batch ) {
188 - $dbw->update( 'page',
189 - array( 'page_touched' => $timestamp ),
190 - array( 'page_id IN (' . $dbw->makeList( $batch ) . ')' ),
191 - __METHOD__
192 - );
193 - }
194 -
195 - # Update squid
196 - if ( $wgUseSquid ) {
197 - $u = SquidUpdate::newFromTitles( $titleArray );
198 - $u->doUpdate();
199 - }
200 -
201 - # Update file cache
202 - if ( $wgUseFileCache ) {
203 - foreach ( $titleArray as $title ) {
204 - HTMLFileCache::clearFileCache( $title );
205 - }
206 - }
207 - }
208 -
209 -}
210 -
211 -/**
212 - * Job wrapper for HTMLCacheUpdate. Gets run whenever a related
213 - * job gets called from the queue.
214 - *
215 - * @ingroup JobQueue
216 - */
217 -class HTMLCacheUpdateJob extends Job {
218 - var $table, $start, $end;
219 -
220 - /**
221 - * Construct a job
222 - * @param $title Title: the title linked to
223 - * @param $params Array: job parameters (table, start and end page_ids)
224 - * @param $id Integer: job id
225 - */
226 - function __construct( $title, $params, $id = 0 ) {
227 - parent::__construct( 'htmlCacheUpdate', $title, $params, $id );
228 - $this->table = $params['table'];
229 - $this->start = $params['start'];
230 - $this->end = $params['end'];
231 - }
232 -
233 - public function run() {
234 - $update = new HTMLCacheUpdate( $this->title, $this->table, $this->start, $this->end );
235 - $update->doUpdate();
236 - return true;
237 - }
238 -}
Index: trunk/phase3/includes/AutoLoader.php
@@ -27,7 +27,6 @@
2828 'BadTitle' => 'includes/Title.php',
2929 'BaseTemplate' => 'includes/SkinTemplate.php',
3030 'Block' => 'includes/Block.php',
31 - 'CacheDependency' => 'includes/CacheDependency.php',
3231 'Category' => 'includes/Category.php',
3332 'Categoryfinder' => 'includes/Categoryfinder.php',
3433 'CategoryPage' => 'includes/CategoryPage.php',
@@ -48,11 +47,9 @@
4948 'ConfEditor' => 'includes/ConfEditor.php',
5049 'ConfEditorParseError' => 'includes/ConfEditor.php',
5150 'ConfEditorToken' => 'includes/ConfEditor.php',
52 - 'ConstantDependency' => 'includes/CacheDependency.php',
5351 'Cookie' => 'includes/Cookie.php',
5452 'CookieJar' => 'includes/Cookie.php',
5553 'CreativeCommonsRdf' => 'includes/Metadata.php',
56 - 'DependencyWrapper' => 'includes/CacheDependency.php',
5754 'DiffHistoryBlob' => 'includes/HistoryBlob.php',
5855 'DjVuImage' => 'includes/DjVuImage.php',
5956 'DoubleReplacer' => 'includes/StringUtils.php',
@@ -87,13 +84,11 @@
8885 'FeedItem' => 'includes/Feed.php',
8986 'FeedUtils' => 'includes/FeedUtils.php',
9087 'FileDeleteForm' => 'includes/FileDeleteForm.php',
91 - 'FileDependency' => 'includes/CacheDependency.php',
9288 'FileRevertForm' => 'includes/FileRevertForm.php',
9389 'ForkController' => 'includes/ForkController.php',
9490 'FormOptions' => 'includes/FormOptions.php',
9591 'FormSpecialPage' => 'includes/SpecialPage.php',
9692 'GenderCache' => 'includes/GenderCache.php',
97 - 'GlobalDependency' => 'includes/CacheDependency.php',
9893 'HashtableReplacer' => 'includes/StringUtils.php',
9994 'HistoryBlob' => 'includes/HistoryBlob.php',
10095 'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
@@ -102,11 +97,8 @@
10398 'HistoryPager' => 'includes/HistoryPage.php',
10499 'Hooks' => 'includes/Hooks.php',
105100 'Html' => 'includes/Html.php',
106 - 'HTMLCacheUpdate' => 'includes/HTMLCacheUpdate.php',
107 - 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php',
108101 'HTMLCheckField' => 'includes/HTMLForm.php',
109102 'HTMLEditTools' => 'includes/HTMLForm.php',
110 - 'HTMLFileCache' => 'includes/HTMLFileCache.php',
111103 'HTMLFloatField' => 'includes/HTMLForm.php',
112104 'HTMLForm' => 'includes/HTMLForm.php',
113105 'HTMLFormField' => 'includes/HTMLForm.php',
@@ -229,8 +221,6 @@
230222 'Title' => 'includes/Title.php',
231223 'TitleArray' => 'includes/TitleArray.php',
232224 'TitleArrayFromResult' => 'includes/TitleArray.php',
233 - 'TitleDependency' => 'includes/CacheDependency.php',
234 - 'TitleListDependency' => 'includes/CacheDependency.php',
235225 'ThrottledError' => 'includes/Exception.php',
236226 'UnlistedSpecialPage' => 'includes/SpecialPage.php',
237227 'UppercaseCollation' => 'includes/Collation.php',
@@ -356,6 +346,18 @@
357347 'ApiUserrights' => 'includes/api/ApiUserrights.php',
358348 'ApiWatch' => 'includes/api/ApiWatch.php',
359349
 350+ # includes/cache
 351+ 'CacheDependency' => 'includes/cache/CacheDependency.php',
 352+ 'ConstantDependency' => 'includes/cache/CacheDependency.php',
 353+ 'DependencyWrapper' => 'includes/cache/CacheDependency.php',
 354+ 'FileDependency' => 'includes/cache/CacheDependency.php',
 355+ 'GlobalDependency' => 'includes/cache/CacheDependency.php',
 356+ 'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
 357+ 'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
 358+ 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
 359+ 'TitleDependency' => 'includes/cache/CacheDependency.php',
 360+ 'TitleListDependency' => 'includes/cache/CacheDependency.php',
 361+
360362 'UsageException' => 'includes/api/ApiMain.php',
361363
362364 # includes/db
Index: trunk/phase3/includes/cache/HTMLCacheUpdate.php
@@ -0,0 +1,237 @@
 2+<?php
 3+
 4+/**
 5+ * Class to invalidate the HTML cache of all the pages linking to a given title.
 6+ * Small numbers of links will be done immediately, large numbers are pushed onto
 7+ * the job queue.
 8+ *
 9+ * This class is designed to work efficiently with small numbers of links, and
 10+ * to work reasonably well with up to ~10^5 links. Above ~10^6 links, the memory
 11+ * and time requirements of loading all backlinked IDs in doUpdate() might become
 12+ * prohibitive. The requirements measured at Wikimedia are approximately:
 13+ *
 14+ * memory: 48 bytes per row
 15+ * time: 16us per row for the query plus processing
 16+ *
 17+ * The reason this query is done is to support partitioning of the job
 18+ * by backlinked ID. The memory issue could be allieviated by doing this query in
 19+ * batches, but of course LIMIT with an offset is inefficient on the DB side.
 20+ *
 21+ * The class is nevertheless a vast improvement on the previous method of using
 22+ * File::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
 23+ * link.
 24+ *
 25+ * @ingroup Cache
 26+ */
 27+class HTMLCacheUpdate
 28+{
 29+ /**
 30+ * @var Title
 31+ */
 32+ public $mTitle;
 33+
 34+ public $mTable, $mPrefix, $mStart, $mEnd;
 35+ public $mRowsPerJob, $mRowsPerQuery;
 36+
 37+ function __construct( $titleTo, $table, $start = false, $end = false ) {
 38+ global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
 39+
 40+ $this->mTitle = $titleTo;
 41+ $this->mTable = $table;
 42+ $this->mStart = $start;
 43+ $this->mEnd = $end;
 44+ $this->mRowsPerJob = $wgUpdateRowsPerJob;
 45+ $this->mRowsPerQuery = $wgUpdateRowsPerQuery;
 46+ $this->mCache = $this->mTitle->getBacklinkCache();
 47+ }
 48+
 49+ public function doUpdate() {
 50+ if ( $this->mStart || $this->mEnd ) {
 51+ $this->doPartialUpdate();
 52+ return;
 53+ }
 54+
 55+ # Get an estimate of the number of rows from the BacklinkCache
 56+ $numRows = $this->mCache->getNumLinks( $this->mTable );
 57+ if ( $numRows > $this->mRowsPerJob * 2 ) {
 58+ # Do fast cached partition
 59+ $this->insertJobs();
 60+ } else {
 61+ # Get the links from the DB
 62+ $titleArray = $this->mCache->getLinks( $this->mTable );
 63+ # Check if the row count estimate was correct
 64+ if ( $titleArray->count() > $this->mRowsPerJob * 2 ) {
 65+ # Not correct, do accurate partition
 66+ wfDebug( __METHOD__.": row count estimate was incorrect, repartitioning\n" );
 67+ $this->insertJobsFromTitles( $titleArray );
 68+ } else {
 69+ $this->invalidateTitles( $titleArray );
 70+ }
 71+ }
 72+ }
 73+
 74+ /**
 75+ * Update some of the backlinks, defined by a page ID range
 76+ */
 77+ protected function doPartialUpdate() {
 78+ $titleArray = $this->mCache->getLinks( $this->mTable, $this->mStart, $this->mEnd );
 79+ if ( $titleArray->count() <= $this->mRowsPerJob * 2 ) {
 80+ # This partition is small enough, do the update
 81+ $this->invalidateTitles( $titleArray );
 82+ } else {
 83+ # Partitioning was excessively inaccurate. Divide the job further.
 84+ # This can occur when a large number of links are added in a short
 85+ # period of time, say by updating a heavily-used template.
 86+ $this->insertJobsFromTitles( $titleArray );
 87+ }
 88+ }
 89+
 90+ /**
 91+ * Partition the current range given by $this->mStart and $this->mEnd,
 92+ * using a pre-calculated title array which gives the links in that range.
 93+ * Queue the resulting jobs.
 94+ */
 95+ protected function insertJobsFromTitles( $titleArray ) {
 96+ # We make subpartitions in the sense that the start of the first job
 97+ # will be the start of the parent partition, and the end of the last
 98+ # job will be the end of the parent partition.
 99+ $jobs = array();
 100+ $start = $this->mStart; # start of the current job
 101+ $numTitles = 0;
 102+ foreach ( $titleArray as $title ) {
 103+ $id = $title->getArticleID();
 104+ # $numTitles is now the number of titles in the current job not
 105+ # including the current ID
 106+ if ( $numTitles >= $this->mRowsPerJob ) {
 107+ # Add a job up to but not including the current ID
 108+ $params = array(
 109+ 'table' => $this->mTable,
 110+ 'start' => $start,
 111+ 'end' => $id - 1
 112+ );
 113+ $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
 114+ $start = $id;
 115+ $numTitles = 0;
 116+ }
 117+ $numTitles++;
 118+ }
 119+ # Last job
 120+ $params = array(
 121+ 'table' => $this->mTable,
 122+ 'start' => $start,
 123+ 'end' => $this->mEnd
 124+ );
 125+ $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
 126+ wfDebug( __METHOD__.": repartitioning into " . count( $jobs ) . " jobs\n" );
 127+
 128+ if ( count( $jobs ) < 2 ) {
 129+ # I don't think this is possible at present, but handling this case
 130+ # makes the code a bit more robust against future code updates and
 131+ # avoids a potential infinite loop of repartitioning
 132+ wfDebug( __METHOD__.": repartitioning failed!\n" );
 133+ $this->invalidateTitles( $titleArray );
 134+ return;
 135+ }
 136+
 137+ Job::batchInsert( $jobs );
 138+ }
 139+
 140+ protected function insertJobs() {
 141+ $batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob );
 142+ if ( !$batches ) {
 143+ return;
 144+ }
 145+ $jobs = array();
 146+ foreach ( $batches as $batch ) {
 147+ $params = array(
 148+ 'table' => $this->mTable,
 149+ 'start' => $batch[0],
 150+ 'end' => $batch[1],
 151+ );
 152+ $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
 153+ }
 154+ Job::batchInsert( $jobs );
 155+ }
 156+
 157+ /**
 158+ * Invalidate a range of pages, right now
 159+ * @deprecated
 160+ */
 161+ public function invalidate( $startId = false, $endId = false ) {
 162+ $titleArray = $this->mCache->getLinks( $this->mTable, $startId, $endId );
 163+ $this->invalidateTitles( $titleArray );
 164+ }
 165+
 166+ /**
 167+ * Invalidate an array (or iterator) of Title objects, right now
 168+ */
 169+ protected function invalidateTitles( $titleArray ) {
 170+ global $wgUseFileCache, $wgUseSquid;
 171+
 172+ $dbw = wfGetDB( DB_MASTER );
 173+ $timestamp = $dbw->timestamp();
 174+
 175+ # Get all IDs in this query into an array
 176+ $ids = array();
 177+ foreach ( $titleArray as $title ) {
 178+ $ids[] = $title->getArticleID();
 179+ }
 180+
 181+ if ( !$ids ) {
 182+ return;
 183+ }
 184+
 185+ # Update page_touched
 186+ $batches = array_chunk( $ids, $this->mRowsPerQuery );
 187+ foreach ( $batches as $batch ) {
 188+ $dbw->update( 'page',
 189+ array( 'page_touched' => $timestamp ),
 190+ array( 'page_id IN (' . $dbw->makeList( $batch ) . ')' ),
 191+ __METHOD__
 192+ );
 193+ }
 194+
 195+ # Update squid
 196+ if ( $wgUseSquid ) {
 197+ $u = SquidUpdate::newFromTitles( $titleArray );
 198+ $u->doUpdate();
 199+ }
 200+
 201+ # Update file cache
 202+ if ( $wgUseFileCache ) {
 203+ foreach ( $titleArray as $title ) {
 204+ HTMLFileCache::clearFileCache( $title );
 205+ }
 206+ }
 207+ }
 208+
 209+}
 210+
 211+/**
 212+ * Job wrapper for HTMLCacheUpdate. Gets run whenever a related
 213+ * job gets called from the queue.
 214+ *
 215+ * @ingroup JobQueue
 216+ */
 217+class HTMLCacheUpdateJob extends Job {
 218+ var $table, $start, $end;
 219+
 220+ /**
 221+ * Construct a job
 222+ * @param $title Title: the title linked to
 223+ * @param $params Array: job parameters (table, start and end page_ids)
 224+ * @param $id Integer: job id
 225+ */
 226+ function __construct( $title, $params, $id = 0 ) {
 227+ parent::__construct( 'htmlCacheUpdate', $title, $params, $id );
 228+ $this->table = $params['table'];
 229+ $this->start = $params['start'];
 230+ $this->end = $params['end'];
 231+ }
 232+
 233+ public function run() {
 234+ $update = new HTMLCacheUpdate( $this->title, $this->table, $this->start, $this->end );
 235+ $update->doUpdate();
 236+ return true;
 237+ }
 238+}
Property changes on: trunk/phase3/includes/cache/HTMLCacheUpdate.php
___________________________________________________________________
Added: svn:eol-style
1239 + native
Index: trunk/phase3/includes/cache/CacheDependency.php
@@ -0,0 +1,367 @@
 2+<?php
 3+/**
 4+ * This class stores an arbitrary value along with its dependencies.
 5+ * Users should typically only use DependencyWrapper::getValueFromCache(),
 6+ * rather than instantiating one of these objects directly.
 7+ * @ingroup Cache
 8+ */
 9+
 10+class DependencyWrapper {
 11+ var $value;
 12+ var $deps;
 13+
 14+ /**
 15+ * Create an instance.
 16+ * @param $value Mixed: the user-supplied value
 17+ * @param $deps Mixed: a dependency or dependency array. All dependencies
 18+ * must be objects implementing CacheDependency.
 19+ */
 20+ function __construct( $value = false, $deps = array() ) {
 21+ $this->value = $value;
 22+
 23+ if ( !is_array( $deps ) ) {
 24+ $deps = array( $deps );
 25+ }
 26+
 27+ $this->deps = $deps;
 28+ }
 29+
 30+ /**
 31+ * Returns true if any of the dependencies have expired
 32+ */
 33+ function isExpired() {
 34+ foreach ( $this->deps as $dep ) {
 35+ if ( $dep->isExpired() ) {
 36+ return true;
 37+ }
 38+ }
 39+
 40+ return false;
 41+ }
 42+
 43+ /**
 44+ * Initialise dependency values in preparation for storing. This must be
 45+ * called before serialization.
 46+ */
 47+ function initialiseDeps() {
 48+ foreach ( $this->deps as $dep ) {
 49+ $dep->loadDependencyValues();
 50+ }
 51+ }
 52+
 53+ /**
 54+ * Get the user-defined value
 55+ */
 56+ function getValue() {
 57+ return $this->value;
 58+ }
 59+
 60+ /**
 61+ * Store the wrapper to a cache
 62+ */
 63+ function storeToCache( $cache, $key, $expiry = 0 ) {
 64+ $this->initialiseDeps();
 65+ $cache->set( $key, $this, $expiry );
 66+ }
 67+
 68+ /**
 69+ * Attempt to get a value from the cache. If the value is expired or missing,
 70+ * it will be generated with the callback function (if present), and the newly
 71+ * calculated value will be stored to the cache in a wrapper.
 72+ *
 73+ * @param $cache Object: a cache object such as $wgMemc
 74+ * @param $key String: the cache key
 75+ * @param $expiry Integer: the expiry timestamp or interval in seconds
 76+ * @param $callback Mixed: the callback for generating the value, or false
 77+ * @param $callbackParams Array: the function parameters for the callback
 78+ * @param $deps Array: the dependencies to store on a cache miss. Note: these
 79+ * are not the dependencies used on a cache hit! Cache hits use the stored
 80+ * dependency array.
 81+ *
 82+ * @return mixed The value, or null if it was not present in the cache and no
 83+ * callback was defined.
 84+ */
 85+ static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false,
 86+ $callbackParams = array(), $deps = array() )
 87+ {
 88+ $obj = $cache->get( $key );
 89+
 90+ if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
 91+ $value = $obj->value;
 92+ } elseif ( $callback ) {
 93+ $value = call_user_func_array( $callback, $callbackParams );
 94+ # Cache the newly-generated value
 95+ $wrapper = new DependencyWrapper( $value, $deps );
 96+ $wrapper->storeToCache( $cache, $key, $expiry );
 97+ } else {
 98+ $value = null;
 99+ }
 100+
 101+ return $value;
 102+ }
 103+}
 104+
 105+/**
 106+ * @ingroup Cache
 107+ */
 108+abstract class CacheDependency {
 109+ /**
 110+ * Returns true if the dependency is expired, false otherwise
 111+ */
 112+ abstract function isExpired();
 113+
 114+ /**
 115+ * Hook to perform any expensive pre-serialize loading of dependency values.
 116+ */
 117+ function loadDependencyValues() { }
 118+}
 119+
 120+/**
 121+ * @ingroup Cache
 122+ */
 123+class FileDependency extends CacheDependency {
 124+ var $filename, $timestamp;
 125+
 126+ /**
 127+ * Create a file dependency
 128+ *
 129+ * @param $filename String: the name of the file, preferably fully qualified
 130+ * @param $timestamp Mixed: the unix last modified timestamp, or false if the
 131+ * file does not exist. If omitted, the timestamp will be loaded from
 132+ * the file.
 133+ *
 134+ * A dependency on a nonexistent file will be triggered when the file is
 135+ * created. A dependency on an existing file will be triggered when the
 136+ * file is changed.
 137+ */
 138+ function __construct( $filename, $timestamp = null ) {
 139+ $this->filename = $filename;
 140+ $this->timestamp = $timestamp;
 141+ }
 142+
 143+ function __sleep() {
 144+ $this->loadDependencyValues();
 145+ return array( 'filename', 'timestamp' );
 146+ }
 147+
 148+ function loadDependencyValues() {
 149+ if ( is_null( $this->timestamp ) ) {
 150+ if ( !file_exists( $this->filename ) ) {
 151+ # Dependency on a non-existent file
 152+ # This is a valid concept!
 153+ $this->timestamp = false;
 154+ } else {
 155+ $this->timestamp = filemtime( $this->filename );
 156+ }
 157+ }
 158+ }
 159+
 160+ function isExpired() {
 161+ if ( !file_exists( $this->filename ) ) {
 162+ if ( $this->timestamp === false ) {
 163+ # Still nonexistent
 164+ return false;
 165+ } else {
 166+ # Deleted
 167+ wfDebug( "Dependency triggered: {$this->filename} deleted.\n" );
 168+ return true;
 169+ }
 170+ } else {
 171+ $lastmod = filemtime( $this->filename );
 172+ if ( $lastmod > $this->timestamp ) {
 173+ # Modified or created
 174+ wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
 175+ return true;
 176+ } else {
 177+ # Not modified
 178+ return false;
 179+ }
 180+ }
 181+ }
 182+}
 183+
 184+/**
 185+ * @ingroup Cache
 186+ */
 187+class TitleDependency extends CacheDependency {
 188+ var $titleObj;
 189+ var $ns, $dbk;
 190+ var $touched;
 191+
 192+ /**
 193+ * Construct a title dependency
 194+ * @param $title Title
 195+ */
 196+ function __construct( Title $title ) {
 197+ $this->titleObj = $title;
 198+ $this->ns = $title->getNamespace();
 199+ $this->dbk = $title->getDBkey();
 200+ }
 201+
 202+ function loadDependencyValues() {
 203+ $this->touched = $this->getTitle()->getTouched();
 204+ }
 205+
 206+ /**
 207+ * Get rid of bulky Title object for sleep
 208+ */
 209+ function __sleep() {
 210+ return array( 'ns', 'dbk', 'touched' );
 211+ }
 212+
 213+ function getTitle() {
 214+ if ( !isset( $this->titleObj ) ) {
 215+ $this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
 216+ }
 217+
 218+ return $this->titleObj;
 219+ }
 220+
 221+ function isExpired() {
 222+ $touched = $this->getTitle()->getTouched();
 223+
 224+ if ( $this->touched === false ) {
 225+ if ( $touched === false ) {
 226+ # Still missing
 227+ return false;
 228+ } else {
 229+ # Created
 230+ return true;
 231+ }
 232+ } elseif ( $touched === false ) {
 233+ # Deleted
 234+ return true;
 235+ } elseif ( $touched > $this->touched ) {
 236+ # Updated
 237+ return true;
 238+ } else {
 239+ # Unmodified
 240+ return false;
 241+ }
 242+ }
 243+}
 244+
 245+/**
 246+ * @ingroup Cache
 247+ */
 248+class TitleListDependency extends CacheDependency {
 249+ var $linkBatch;
 250+ var $timestamps;
 251+
 252+ /**
 253+ * Construct a dependency on a list of titles
 254+ */
 255+ function __construct( LinkBatch $linkBatch ) {
 256+ $this->linkBatch = $linkBatch;
 257+ }
 258+
 259+ function calculateTimestamps() {
 260+ # Initialise values to false
 261+ $timestamps = array();
 262+
 263+ foreach ( $this->getLinkBatch()->data as $ns => $dbks ) {
 264+ if ( count( $dbks ) > 0 ) {
 265+ $timestamps[$ns] = array();
 266+
 267+ foreach ( $dbks as $dbk => $value ) {
 268+ $timestamps[$ns][$dbk] = false;
 269+ }
 270+ }
 271+ }
 272+
 273+ # Do the query
 274+ if ( count( $timestamps ) ) {
 275+ $dbr = wfGetDB( DB_SLAVE );
 276+ $where = $this->getLinkBatch()->constructSet( 'page', $dbr );
 277+ $res = $dbr->select(
 278+ 'page',
 279+ array( 'page_namespace', 'page_title', 'page_touched' ),
 280+ $where,
 281+ __METHOD__
 282+ );
 283+
 284+ foreach ( $res as $row ) {
 285+ $timestamps[$row->page_namespace][$row->page_title] = $row->page_touched;
 286+ }
 287+ }
 288+
 289+ return $timestamps;
 290+ }
 291+
 292+ function loadDependencyValues() {
 293+ $this->timestamps = $this->calculateTimestamps();
 294+ }
 295+
 296+ function __sleep() {
 297+ return array( 'timestamps' );
 298+ }
 299+
 300+ function getLinkBatch() {
 301+ if ( !isset( $this->linkBatch ) ) {
 302+ $this->linkBatch = new LinkBatch;
 303+ $this->linkBatch->setArray( $this->timestamps );
 304+ }
 305+ return $this->linkBatch;
 306+ }
 307+
 308+ function isExpired() {
 309+ $newTimestamps = $this->calculateTimestamps();
 310+
 311+ foreach ( $this->timestamps as $ns => $dbks ) {
 312+ foreach ( $dbks as $dbk => $oldTimestamp ) {
 313+ $newTimestamp = $newTimestamps[$ns][$dbk];
 314+
 315+ if ( $oldTimestamp === false ) {
 316+ if ( $newTimestamp === false ) {
 317+ # Still missing
 318+ } else {
 319+ # Created
 320+ return true;
 321+ }
 322+ } elseif ( $newTimestamp === false ) {
 323+ # Deleted
 324+ return true;
 325+ } elseif ( $newTimestamp > $oldTimestamp ) {
 326+ # Updated
 327+ return true;
 328+ } else {
 329+ # Unmodified
 330+ }
 331+ }
 332+ }
 333+
 334+ return false;
 335+ }
 336+}
 337+
 338+/**
 339+ * @ingroup Cache
 340+ */
 341+class GlobalDependency extends CacheDependency {
 342+ var $name, $value;
 343+
 344+ function __construct( $name ) {
 345+ $this->name = $name;
 346+ $this->value = $GLOBALS[$name];
 347+ }
 348+
 349+ function isExpired() {
 350+ return $GLOBALS[$this->name] != $this->value;
 351+ }
 352+}
 353+
 354+/**
 355+ * @ingroup Cache
 356+ */
 357+class ConstantDependency extends CacheDependency {
 358+ var $name, $value;
 359+
 360+ function __construct( $name ) {
 361+ $this->name = $name;
 362+ $this->value = constant( $name );
 363+ }
 364+
 365+ function isExpired() {
 366+ return constant( $this->name ) != $this->value;
 367+ }
 368+}
Property changes on: trunk/phase3/includes/cache/CacheDependency.php
___________________________________________________________________
Added: svn:eol-style
1369 + native
Index: trunk/phase3/includes/cache/HTMLFileCache.php
@@ -0,0 +1,250 @@
 2+<?php
 3+/**
 4+ * Contain the HTMLFileCache class
 5+ * @file
 6+ * @ingroup Cache
 7+ */
 8+
 9+/**
 10+ * Handles talking to the file cache, putting stuff in and taking it back out.
 11+ * Mostly called from Article.php for the emergency abort/fallback to cache.
 12+ *
 13+ * Global options that affect this module:
 14+ * - $wgCachePages
 15+ * - $wgCacheEpoch
 16+ * - $wgUseFileCache
 17+ * - $wgCacheDirectory
 18+ * - $wgFileCacheDirectory
 19+ * - $wgUseGzip
 20+ *
 21+ * @ingroup Cache
 22+ */
 23+class HTMLFileCache {
 24+
 25+ /**
 26+ * @var Title
 27+ */
 28+ var $mTitle;
 29+ var $mFileCache, $mType;
 30+
 31+ public function __construct( &$title, $type = 'view' ) {
 32+ $this->mTitle = $title;
 33+ $this->mType = ($type == 'raw' || $type == 'view' ) ? $type : false;
 34+ $this->fileCacheName(); // init name
 35+ }
 36+
 37+ public function fileCacheName() {
 38+ if( !$this->mFileCache ) {
 39+ global $wgCacheDirectory, $wgFileCacheDirectory, $wgFileCacheDepth;
 40+
 41+ if ( $wgFileCacheDirectory ) {
 42+ $dir = $wgFileCacheDirectory;
 43+ } elseif ( $wgCacheDirectory ) {
 44+ $dir = "$wgCacheDirectory/html";
 45+ } else {
 46+ throw new MWException( 'Please set $wgCacheDirectory in LocalSettings.php if you wish to use the HTML file cache' );
 47+ }
 48+
 49+ # Store raw pages (like CSS hits) elsewhere
 50+ $subdir = ($this->mType === 'raw') ? 'raw/' : '';
 51+
 52+ $key = $this->mTitle->getPrefixedDbkey();
 53+ if ( $wgFileCacheDepth > 0 ) {
 54+ $hash = md5( $key );
 55+ for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
 56+ $subdir .= substr( $hash, 0, $i ) . '/';
 57+ }
 58+ }
 59+ # Avoid extension confusion
 60+ $key = str_replace( '.', '%2E', urlencode( $key ) );
 61+ $this->mFileCache = "{$dir}/{$subdir}{$key}.html";
 62+
 63+ if( $this->useGzip() ) {
 64+ $this->mFileCache .= '.gz';
 65+ }
 66+
 67+ wfDebug( __METHOD__ . ": {$this->mFileCache}\n" );
 68+ }
 69+ return $this->mFileCache;
 70+ }
 71+
 72+ public function isFileCached() {
 73+ if( $this->mType === false ) {
 74+ return false;
 75+ }
 76+ return file_exists( $this->fileCacheName() );
 77+ }
 78+
 79+ public function fileCacheTime() {
 80+ return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) );
 81+ }
 82+
 83+ /**
 84+ * Check if pages can be cached for this request/user
 85+ * @return bool
 86+ */
 87+ public static function useFileCache() {
 88+ global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang;
 89+ if( !$wgUseFileCache ) {
 90+ return false;
 91+ }
 92+ // Get all query values
 93+ $queryVals = $wgRequest->getValues();
 94+ foreach( $queryVals as $query => $val ) {
 95+ if( $query == 'title' || $query == 'curid' ) {
 96+ continue;
 97+ }
 98+ // Normal page view in query form can have action=view.
 99+ // Raw hits for pages also stored, like .css pages for example.
 100+ else if( $query == 'action' && ($val == 'view' || $val == 'raw') ) {
 101+ continue;
 102+ } else if( $query == 'usemsgcache' && $val == 'yes' ) {
 103+ continue;
 104+ }
 105+ // Below are header setting params
 106+ else if( $query == 'maxage' || $query == 'smaxage' || $query == 'ctype' || $query == 'gen' ) {
 107+ continue;
 108+ } else {
 109+ return false;
 110+ }
 111+ }
 112+ // Check for non-standard user language; this covers uselang,
 113+ // and extensions for auto-detecting user language.
 114+ $ulang = $wgLang->getCode();
 115+ $clang = $wgContLang->getCode();
 116+ // Check that there are no other sources of variation
 117+ return !$wgShowIPinHeader && !$wgUser->getId() && !$wgUser->getNewtalk() && $ulang == $clang;
 118+ }
 119+
 120+ /*
 121+ * Check if up to date cache file exists
 122+ * @param $timestamp string
 123+ */
 124+ public function isFileCacheGood( $timestamp = '' ) {
 125+ global $wgCacheEpoch;
 126+
 127+ if( !$this->isFileCached() ) {
 128+ return false;
 129+ }
 130+
 131+ $cachetime = $this->fileCacheTime();
 132+ $good = $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime;
 133+
 134+ wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n");
 135+ return $good;
 136+ }
 137+
 138+ public function useGzip() {
 139+ global $wgUseGzip;
 140+ return $wgUseGzip;
 141+ }
 142+
 143+ /* In handy string packages */
 144+ public function fetchRawText() {
 145+ return file_get_contents( $this->fileCacheName() );
 146+ }
 147+
 148+ public function fetchPageText() {
 149+ if( $this->useGzip() ) {
 150+ /* Why is there no gzfile_get_contents() or gzdecode()? */
 151+ return implode( '', gzfile( $this->fileCacheName() ) );
 152+ } else {
 153+ return $this->fetchRawText();
 154+ }
 155+ }
 156+
 157+ /* Working directory to/from output */
 158+ public function loadFromFileCache() {
 159+ global $wgOut, $wgMimeType, $wgOutputEncoding, $wgLanguageCode;
 160+ wfDebug( __METHOD__ . "()\n");
 161+ $filename = $this->fileCacheName();
 162+ // Raw pages should handle cache control on their own,
 163+ // even when using file cache. This reduces hits from clients.
 164+ if( $this->mType !== 'raw' ) {
 165+ $wgOut->sendCacheControl();
 166+ header( "Content-Type: $wgMimeType; charset={$wgOutputEncoding}" );
 167+ header( "Content-Language: $wgLanguageCode" );
 168+ }
 169+
 170+ if( $this->useGzip() ) {
 171+ if( wfClientAcceptsGzip() ) {
 172+ header( 'Content-Encoding: gzip' );
 173+ } else {
 174+ /* Send uncompressed */
 175+ readgzfile( $filename );
 176+ return;
 177+ }
 178+ }
 179+ readfile( $filename );
 180+ $wgOut->disable(); // tell $wgOut that output is taken care of
 181+ }
 182+
 183+ protected function checkCacheDirs() {
 184+ $filename = $this->fileCacheName();
 185+ $mydir2 = substr($filename,0,strrpos($filename,'/')); # subdirectory level 2
 186+ $mydir1 = substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1
 187+
 188+ wfMkdirParents( $mydir1 );
 189+ wfMkdirParents( $mydir2 );
 190+ }
 191+
 192+ public function saveToFileCache( $text ) {
 193+ global $wgUseFileCache;
 194+ if( !$wgUseFileCache || strlen( $text ) < 512 ) {
 195+ // Disabled or empty/broken output (OOM and PHP errors)
 196+ return $text;
 197+ }
 198+
 199+ wfDebug( __METHOD__ . "()\n", false);
 200+
 201+ $this->checkCacheDirs();
 202+
 203+ $f = fopen( $this->fileCacheName(), 'w' );
 204+ if($f) {
 205+ $now = wfTimestampNow();
 206+ if( $this->useGzip() ) {
 207+ $rawtext = str_replace( '</html>',
 208+ '<!-- Cached/compressed '.$now." -->\n</html>",
 209+ $text );
 210+ $text = gzencode( $rawtext );
 211+ } else {
 212+ $text = str_replace( '</html>',
 213+ '<!-- Cached '.$now." -->\n</html>",
 214+ $text );
 215+ }
 216+ fwrite( $f, $text );
 217+ fclose( $f );
 218+ if( $this->useGzip() ) {
 219+ if( wfClientAcceptsGzip() ) {
 220+ header( 'Content-Encoding: gzip' );
 221+ return $text;
 222+ } else {
 223+ return $rawtext;
 224+ }
 225+ } else {
 226+ return $text;
 227+ }
 228+ }
 229+ return $text;
 230+ }
 231+
 232+ public static function clearFileCache( $title ) {
 233+ global $wgUseFileCache;
 234+
 235+ if ( !$wgUseFileCache ) {
 236+ return false;
 237+ }
 238+
 239+ wfSuppressWarnings();
 240+
 241+ $fc = new self( $title, 'view' );
 242+ unlink( $fc->fileCacheName() );
 243+
 244+ $fc = new self( $title, 'raw' );
 245+ unlink( $fc->fileCacheName() );
 246+
 247+ wfRestoreWarnings();
 248+
 249+ return true;
 250+ }
 251+}
Property changes on: trunk/phase3/includes/cache/HTMLFileCache.php
___________________________________________________________________
Added: svn:eol-style
1252 + native
Added: svn:keywords
2253 + Author Date Id Revision

Comments

#Comment by 😂 (talk | contribs)   21:36, 25 April 2011

Maybe it should be htmlcache/, not just cache/? Since we already have objectcache/, that is.

Either that or move all caching-related things to cache/ and nuke objectcache/

#Comment by Reedy (talk | contribs)   21:39, 25 April 2011

Hmm. Seems sensible to probably just move it to one "cache" folder

I have moved more, but I'd guess there are still more we can stick in there...

Status & tagging log