Index: trunk/phase3/includes/BagOStuff.php |
— | — | @@ -37,129 +37,122 @@ |
38 | 38 | * |
39 | 39 | * @ingroup Cache |
40 | 40 | */ |
41 | | -class BagOStuff { |
42 | | - var $debugmode; |
| 41 | +abstract class BagOStuff { |
| 42 | + var $debugMode = false; |
43 | 43 | |
44 | | - function __construct() { |
45 | | - $this->set_debug( false ); |
| 44 | + public function set_debug( $bool ) { |
| 45 | + $this->debugMode = $bool; |
46 | 46 | } |
47 | 47 | |
48 | | - function set_debug($bool) { |
49 | | - $this->debugmode = $bool; |
50 | | - } |
51 | | - |
52 | 48 | /* *** THE GUTS OF THE OPERATION *** */ |
53 | 49 | /* Override these with functional things in subclasses */ |
54 | 50 | |
55 | | - function get($key) { |
56 | | - /* stub */ |
57 | | - return false; |
58 | | - } |
| 51 | + /** |
| 52 | + * Get an item with the given key. Returns false if it does not exist. |
| 53 | + * @param $key string |
| 54 | + */ |
| 55 | + abstract public function get( $key ); |
59 | 56 | |
60 | | - function set($key, $value, $exptime=0) { |
61 | | - /* stub */ |
62 | | - return false; |
63 | | - } |
| 57 | + /** |
| 58 | + * Set an item. |
| 59 | + * @param $key string |
| 60 | + * @param $value mixed |
| 61 | + * @param $exptime int Either an interval in seconds or a unix timestamp for expiry |
| 62 | + */ |
| 63 | + abstract public function set( $key, $value, $exptime = 0 ); |
64 | 64 | |
65 | | - function delete($key, $time=0) { |
66 | | - /* stub */ |
67 | | - return false; |
68 | | - } |
| 65 | + /* |
| 66 | + * Delete an item. |
| 67 | + * @param $key string |
| 68 | + * @param $time int Amount of time to delay the operation (mostly memcached-specific) |
| 69 | + */ |
| 70 | + abstract public function delete( $key, $time = 0 ); |
69 | 71 | |
70 | | - function lock($key, $timeout = 0) { |
| 72 | + public function lock( $key, $timeout = 0 ) { |
71 | 73 | /* stub */ |
72 | 74 | return true; |
73 | 75 | } |
74 | 76 | |
75 | | - function unlock($key) { |
| 77 | + public function unlock( $key ) { |
76 | 78 | /* stub */ |
77 | 79 | return true; |
78 | 80 | } |
79 | 81 | |
80 | | - function keys() { |
| 82 | + public function keys() { |
81 | 83 | /* stub */ |
82 | 84 | return array(); |
83 | 85 | } |
84 | 86 | |
85 | 87 | /* *** Emulated functions *** */ |
86 | 88 | /* Better performance can likely be got with custom written versions */ |
87 | | - function get_multi($keys) { |
| 89 | + public function get_multi( $keys ) { |
88 | 90 | $out = array(); |
89 | | - foreach($keys as $key) |
90 | | - $out[$key] = $this->get($key); |
| 91 | + foreach ( $keys as $key ) { |
| 92 | + $out[$key] = $this->get( $key ); |
| 93 | + } |
91 | 94 | return $out; |
92 | 95 | } |
93 | 96 | |
94 | | - function set_multi($hash, $exptime=0) { |
95 | | - foreach($hash as $key => $value) |
96 | | - $this->set($key, $value, $exptime); |
| 97 | + public function set_multi( $hash, $exptime = 0 ) { |
| 98 | + foreach ( $hash as $key => $value ) { |
| 99 | + $this->set( $key, $value, $exptime ); |
| 100 | + } |
97 | 101 | } |
98 | 102 | |
99 | | - function add($key, $value, $exptime=0) { |
100 | | - if( $this->get($key) == false ) { |
101 | | - $this->set($key, $value, $exptime); |
| 103 | + public function add( $key, $value, $exptime = 0 ) { |
| 104 | + if ( $this->get( $key ) == false ) { |
| 105 | + $this->set( $key, $value, $exptime ); |
102 | 106 | return true; |
103 | 107 | } |
104 | 108 | } |
105 | 109 | |
106 | | - function add_multi($hash, $exptime=0) { |
107 | | - foreach($hash as $key => $value) |
108 | | - $this->add($key, $value, $exptime); |
| 110 | + public function add_multi( $hash, $exptime = 0 ) { |
| 111 | + foreach ( $hash as $key => $value ) { |
| 112 | + $this->add( $key, $value, $exptime ); |
| 113 | + } |
109 | 114 | } |
110 | 115 | |
111 | | - function delete_multi($keys, $time=0) { |
112 | | - foreach($keys as $key) |
113 | | - $this->delete($key, $time); |
| 116 | + public function delete_multi( $keys, $time = 0 ) { |
| 117 | + foreach ( $keys as $key ) { |
| 118 | + $this->delete( $key, $time ); |
| 119 | + } |
114 | 120 | } |
115 | 121 | |
116 | | - function replace($key, $value, $exptime=0) { |
117 | | - if( $this->get($key) !== false ) |
118 | | - $this->set($key, $value, $exptime); |
| 122 | + public function replace( $key, $value, $exptime = 0 ) { |
| 123 | + if ( $this->get( $key ) !== false ) { |
| 124 | + $this->set( $key, $value, $exptime ); |
| 125 | + } |
119 | 126 | } |
120 | 127 | |
121 | | - function incr($key, $value=1) { |
122 | | - if ( !$this->lock($key) ) { |
| 128 | + public function incr( $key, $value = 1 ) { |
| 129 | + if ( !$this->lock( $key ) ) { |
123 | 130 | return false; |
124 | 131 | } |
125 | | - $value = intval($value); |
126 | | - if($value < 0) $value = 0; |
| 132 | + $value = intval( $value ); |
127 | 133 | |
128 | 134 | $n = false; |
129 | | - if( ($n = $this->get($key)) !== false ) { |
| 135 | + if ( ( $n = $this->get( $key ) ) !== false ) { |
130 | 136 | $n += $value; |
131 | | - $this->set($key, $n); // exptime? |
| 137 | + $this->set( $key, $n ); // exptime? |
132 | 138 | } |
133 | | - $this->unlock($key); |
| 139 | + $this->unlock( $key ); |
134 | 140 | return $n; |
135 | 141 | } |
136 | 142 | |
137 | | - function decr($key, $value=1) { |
138 | | - if ( !$this->lock($key) ) { |
139 | | - return false; |
140 | | - } |
141 | | - $value = intval($value); |
142 | | - if($value < 0) $value = 0; |
143 | | - |
144 | | - $m = false; |
145 | | - if( ($n = $this->get($key)) !== false ) { |
146 | | - $m = $n - $value; |
147 | | - if($m < 0) $m = 0; |
148 | | - $this->set($key, $m); // exptime? |
149 | | - } |
150 | | - $this->unlock($key); |
151 | | - return $m; |
| 143 | + public function decr( $key, $value = 1 ) { |
| 144 | + return $this->incr( $key, -$value ); |
152 | 145 | } |
153 | 146 | |
154 | | - function _debug($text) { |
155 | | - if($this->debugmode) |
156 | | - wfDebug("BagOStuff debug: $text\n"); |
| 147 | + public function debug( $text ) { |
| 148 | + if ( $this->debugMode ) |
| 149 | + wfDebug( "BagOStuff debug: $text\n" ); |
157 | 150 | } |
158 | 151 | |
159 | 152 | /** |
160 | 153 | * Convert an optionally relative time to an absolute time |
161 | 154 | */ |
162 | | - static function convertExpiry( $exptime ) { |
163 | | - if(($exptime != 0) && ($exptime < 3600*24*30)) { |
| 155 | + protected function convertExpiry( $exptime ) { |
| 156 | + if ( ( $exptime != 0 ) && ( $exptime < 3600 * 24 * 30 ) ) { |
164 | 157 | return time() + $exptime; |
165 | 158 | } else { |
166 | 159 | return $exptime; |
— | — | @@ -182,30 +175,34 @@ |
183 | 176 | $this->bag = array(); |
184 | 177 | } |
185 | 178 | |
186 | | - function _expire($key) { |
| 179 | + protected function expire( $key ) { |
187 | 180 | $et = $this->bag[$key][1]; |
188 | | - if(($et == 0) || ($et > time())) |
| 181 | + if ( ( $et == 0 ) || ( $et > time() ) ) { |
189 | 182 | return false; |
190 | | - $this->delete($key); |
| 183 | + } |
| 184 | + $this->delete( $key ); |
191 | 185 | return true; |
192 | 186 | } |
193 | 187 | |
194 | | - function get($key) { |
195 | | - if( !isset( $this->bag[$key] ) ) |
| 188 | + function get( $key ) { |
| 189 | + if ( !isset( $this->bag[$key] ) ) { |
196 | 190 | return false; |
197 | | - if($this->_expire($key)) |
| 191 | + } |
| 192 | + if ( $this->expire( $key ) ) { |
198 | 193 | return false; |
| 194 | + } |
199 | 195 | return $this->bag[$key][0]; |
200 | 196 | } |
201 | 197 | |
202 | | - function set($key,$value,$exptime=0) { |
203 | | - $this->bag[$key] = array( $value, BagOStuff::convertExpiry( $exptime ) ); |
| 198 | + function set( $key, $value, $exptime = 0 ) { |
| 199 | + $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) ); |
204 | 200 | } |
205 | 201 | |
206 | | - function delete($key,$time=0) { |
207 | | - if( !isset( $this->bag[$key] ) ) |
| 202 | + function delete( $key, $time = 0 ) { |
| 203 | + if ( !isset( $this->bag[$key] ) ) { |
208 | 204 | return false; |
209 | | - unset($this->bag[$key]); |
| 205 | + } |
| 206 | + unset( $this->bag[$key] ); |
210 | 207 | return true; |
211 | 208 | } |
212 | 209 | |
— | — | @@ -215,182 +212,187 @@ |
216 | 213 | } |
217 | 214 | |
218 | 215 | /** |
219 | | - * Generic class to store objects in a database |
| 216 | + * Class to store objects in the database |
220 | 217 | * |
221 | 218 | * @ingroup Cache |
222 | 219 | */ |
223 | | -abstract class SqlBagOStuff extends BagOStuff { |
224 | | - var $table; |
225 | | - var $lastexpireall = 0; |
| 220 | +class SqlBagOStuff extends BagOStuff { |
| 221 | + var $lb, $db; |
| 222 | + var $lastExpireAll = 0; |
226 | 223 | |
227 | | - /** |
228 | | - * Constructor |
229 | | - * |
230 | | - * @param $tablename String: name of the table to use |
231 | | - */ |
232 | | - function __construct($tablename = 'objectcache') { |
233 | | - $this->table = $tablename; |
| 224 | + protected function getDB() { |
| 225 | + if ( !isset( $this->lb ) ) { |
| 226 | + $this->lb = wfGetLBFactory()->newMainLB(); |
| 227 | + $this->db = $this->lb->getConnection( DB_MASTER ); |
| 228 | + $this->db->clearFlag( DBO_TRX ); |
| 229 | + } |
| 230 | + return $this->db; |
234 | 231 | } |
235 | 232 | |
236 | | - function get($key) { |
237 | | - /* expire old entries if any */ |
| 233 | + public function get( $key ) { |
| 234 | + # expire old entries if any |
238 | 235 | $this->garbageCollect(); |
239 | | - |
240 | | - $res = $this->_query( |
241 | | - "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key); |
242 | | - if(!$res) { |
243 | | - $this->_debug("get: ** error: " . $this->_dberror($res) . " **"); |
| 236 | + $db = $this->getDB(); |
| 237 | + $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), |
| 238 | + array( 'keyname' => $key ), __METHOD__ ); |
| 239 | + if ( !$row ) { |
| 240 | + $this->debug( 'get: no matching rows' ); |
244 | 241 | return false; |
245 | 242 | } |
246 | | - if($row=$this->_fetchobject($res)) { |
247 | | - $this->_debug("get: retrieved data; exp time is " . $row->exptime); |
248 | | - if ( $row->exptime != $this->_maxdatetime() && |
249 | | - wfTimestamp( TS_UNIX, $row->exptime ) < time() ) |
250 | | - { |
251 | | - $this->_debug("get: key has expired, deleting"); |
252 | | - $this->delete($key); |
253 | | - return false; |
| 243 | + |
| 244 | + $this->debug( "get: retrieved data; expiry time is " . $row->exptime ); |
| 245 | + if ( $this->isExpired( $row->exptime ) ) { |
| 246 | + $this->debug( "get: key has expired, deleting" ); |
| 247 | + try { |
| 248 | + $db->begin(); |
| 249 | + # Put the expiry time in the WHERE condition to avoid deleting a |
| 250 | + # newly-inserted value |
| 251 | + $db->delete( 'objectcache', |
| 252 | + array( |
| 253 | + 'keyname' => $key, |
| 254 | + 'exptime' => $row->exptime |
| 255 | + ), __METHOD__ ); |
| 256 | + $db->commit(); |
| 257 | + } catch ( DBQueryError $e ) { |
| 258 | + $this->handleWriteError( $e ); |
254 | 259 | } |
255 | | - return $this->_unserialize($this->_blobdecode($row->value)); |
256 | | - } else { |
257 | | - $this->_debug('get: no matching rows'); |
| 260 | + return false; |
258 | 261 | } |
259 | | - return false; |
| 262 | + return $this->unserialize( $db->decodeBlob( $row->value ) ); |
260 | 263 | } |
261 | 264 | |
262 | | - function set($key,$value,$exptime=0) { |
263 | | - if ( $this->_readonly() ) { |
264 | | - return false; |
265 | | - } |
266 | | - $exptime = intval($exptime); |
267 | | - if($exptime < 0) $exptime = 0; |
268 | | - if($exptime == 0) { |
269 | | - $exp = $this->_maxdatetime(); |
| 265 | + public function set( $key, $value, $exptime = 0 ) { |
| 266 | + $db = $this->getDB(); |
| 267 | + $exptime = intval( $exptime ); |
| 268 | + if ( $exptime < 0 ) $exptime = 0; |
| 269 | + if ( $exptime == 0 ) { |
| 270 | + $encExpiry = $this->getMaxDateTime(); |
270 | 271 | } else { |
271 | | - if($exptime < 3.16e8) # ~10 years |
| 272 | + if ( $exptime < 3.16e8 ) # ~10 years |
272 | 273 | $exptime += time(); |
273 | | - $exp = $this->_fromunixtime($exptime); |
| 274 | + $encExpiry = $db->timestamp( $exptime ); |
274 | 275 | } |
275 | | - $this->_begin(); |
276 | | - $this->_query( |
277 | | - "DELETE FROM $0 WHERE keyname='$1'", $key ); |
278 | | - $this->_doinsert($this->getTableName(), array( |
| 276 | + try { |
| 277 | + $db->begin(); |
| 278 | + $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); |
| 279 | + $db->insert( 'objectcache', |
| 280 | + array( |
279 | 281 | 'keyname' => $key, |
280 | | - 'value' => $this->_blobencode($this->_serialize($value)), |
281 | | - 'exptime' => $exp |
282 | | - )); |
283 | | - $this->_commit(); |
284 | | - return true; /* ? */ |
| 282 | + 'value' => $db->encodeBlob( $this->serialize( $value ) ), |
| 283 | + 'exptime' => $encExpiry |
| 284 | + ), __METHOD__ ); |
| 285 | + $db->commit(); |
| 286 | + } catch ( DBQueryError $e ) { |
| 287 | + $this->handleWriteError( $e ); |
| 288 | + return false; |
| 289 | + } |
| 290 | + return true; |
285 | 291 | } |
286 | 292 | |
287 | | - function delete($key,$time=0) { |
288 | | - if ( $this->_readonly() ) { |
| 293 | + public function delete( $key, $time = 0 ) { |
| 294 | + $db = $this->getDB(); |
| 295 | + try { |
| 296 | + $db->begin(); |
| 297 | + $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); |
| 298 | + $db->commit(); |
| 299 | + } catch ( DBQueryError $e ) { |
| 300 | + $this->handleWriteError( $e ); |
289 | 301 | return false; |
290 | 302 | } |
291 | | - $this->_begin(); |
292 | | - $this->_query( |
293 | | - "DELETE FROM $0 WHERE keyname='$1'", $key ); |
294 | | - $this->_commit(); |
295 | | - return true; /* ? */ |
| 303 | + return true; |
296 | 304 | } |
297 | 305 | |
298 | | - function keys() { |
299 | | - $res = $this->_query( "SELECT keyname FROM $0" ); |
300 | | - if(!$res) { |
301 | | - $this->_debug("keys: ** error: " . $this->_dberror($res) . " **"); |
302 | | - return array(); |
| 306 | + public function incr( $key, $step = 1 ) { |
| 307 | + $db = $this->getDB(); |
| 308 | + $step = intval( $step ); |
| 309 | + |
| 310 | + try { |
| 311 | + $db->begin(); |
| 312 | + $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), |
| 313 | + array( 'keyname' => $key ), __METHOD__, array( 'FOR UPDATE' ) ); |
| 314 | + if ( $row === false ) { |
| 315 | + // Missing |
| 316 | + $db->commit(); |
| 317 | + return false; |
| 318 | + } |
| 319 | + $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); |
| 320 | + if ( $this->isExpired( $row->exptime ) ) { |
| 321 | + // Expired, do not reinsert |
| 322 | + $db->commit(); |
| 323 | + return false; |
| 324 | + } |
| 325 | + |
| 326 | + $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) ); |
| 327 | + $newValue = $oldValue + $step; |
| 328 | + $db->insert( 'objectcache', |
| 329 | + array( |
| 330 | + 'keyname' => $key, |
| 331 | + 'value' => $db->encodeBlob( $this->serialize( $newValue ) ), |
| 332 | + 'exptime' => $row->exptime |
| 333 | + ), __METHOD__ ); |
| 334 | + $db->commit(); |
| 335 | + } catch ( DBQueryError $e ) { |
| 336 | + $this->handleWriteError( $e ); |
| 337 | + return false; |
303 | 338 | } |
| 339 | + return $newValue; |
| 340 | + } |
| 341 | + |
| 342 | + public function keys() { |
| 343 | + $db = $this->getDB(); |
| 344 | + $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ ); |
304 | 345 | $result = array(); |
305 | | - while( $row = $this->_fetchobject($res) ) { |
| 346 | + foreach ( $res as $row ) { |
306 | 347 | $result[] = $row->keyname; |
307 | 348 | } |
308 | 349 | return $result; |
309 | 350 | } |
310 | 351 | |
311 | | - function getTableName() { |
312 | | - return $this->table; |
| 352 | + protected function isExpired( $exptime ) { |
| 353 | + return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time(); |
313 | 354 | } |
314 | 355 | |
315 | | - function _query($sql) { |
316 | | - $reps = func_get_args(); |
317 | | - $reps[0] = $this->getTableName(); |
318 | | - // ewwww |
319 | | - for($i=0;$i<count($reps);$i++) { |
320 | | - $sql = str_replace( |
321 | | - '$' . $i, |
322 | | - $i > 0 ? $this->_strencode($reps[$i]) : $reps[$i], |
323 | | - $sql); |
| 356 | + protected function getMaxDateTime() { |
| 357 | + if ( time() > 0x7fffffff ) { |
| 358 | + return $this->getDB()->timestamp( 1 << 62 ); |
| 359 | + } else { |
| 360 | + return $this->getDB()->timestamp( 0x7fffffff ); |
324 | 361 | } |
325 | | - $res = $this->_doquery($sql); |
326 | | - if($res == false) { |
327 | | - $this->_debug('query failed: ' . $this->_dberror($res)); |
328 | | - } |
329 | | - return $res; |
330 | 362 | } |
331 | 363 | |
332 | | - function _strencode($str) { |
333 | | - /* Protect strings in SQL */ |
334 | | - return str_replace( "'", "''", $str ); |
335 | | - } |
336 | | - function _blobencode($str) { |
337 | | - return $str; |
338 | | - } |
339 | | - function _blobdecode($str) { |
340 | | - return $str; |
341 | | - } |
342 | | - |
343 | | - abstract function _doinsert($table, $vals); |
344 | | - abstract function _doquery($sql); |
345 | | - |
346 | | - abstract function _readonly(); |
347 | | - |
348 | | - function _begin() {} |
349 | | - function _commit() {} |
350 | | - |
351 | | - function _freeresult($result) { |
352 | | - /* stub */ |
353 | | - return false; |
354 | | - } |
355 | | - |
356 | | - function _dberror($result) { |
357 | | - /* stub */ |
358 | | - return 'unknown error'; |
359 | | - } |
360 | | - |
361 | | - abstract function _maxdatetime(); |
362 | | - abstract function _fromunixtime($ts); |
363 | | - |
364 | | - function garbageCollect() { |
| 364 | + protected function garbageCollect() { |
365 | 365 | /* Ignore 99% of requests */ |
366 | 366 | if ( !mt_rand( 0, 100 ) ) { |
367 | | - $nowtime = time(); |
| 367 | + $now = time(); |
368 | 368 | /* Avoid repeating the delete within a few seconds */ |
369 | | - if ( $nowtime > ($this->lastexpireall + 1) ) { |
370 | | - $this->lastexpireall = $nowtime; |
371 | | - $this->expireall(); |
| 369 | + if ( $now > ( $this->lastExpireAll + 1 ) ) { |
| 370 | + $this->lastExpireAll = $now; |
| 371 | + $this->expireAll(); |
372 | 372 | } |
373 | 373 | } |
374 | 374 | } |
375 | 375 | |
376 | | - function expireall() { |
377 | | - /* Remove any items that have expired */ |
378 | | - if ( $this->_readonly() ) { |
379 | | - return false; |
| 376 | + public function expireAll() { |
| 377 | + $db = $this->getDB(); |
| 378 | + $now = $db->timestamp(); |
| 379 | + try { |
| 380 | + $db->begin(); |
| 381 | + $db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__ ); |
| 382 | + $db->commit(); |
| 383 | + } catch ( DBQueryError $e ) { |
| 384 | + $this->handleWriteError( $e ); |
380 | 385 | } |
381 | | - $now = $this->_fromunixtime( time() ); |
382 | | - $this->_begin(); |
383 | | - $this->_query( "DELETE FROM $0 WHERE exptime < '$now'" ); |
384 | | - $this->_commit(); |
385 | 386 | } |
386 | 387 | |
387 | | - function deleteall() { |
388 | | - /* Clear *all* items from cache table */ |
389 | | - if ( $this->_readonly() ) { |
390 | | - return false; |
| 388 | + public function deleteAll() { |
| 389 | + $db = $this->getDB(); |
| 390 | + try { |
| 391 | + $db->begin(); |
| 392 | + $db->delete( 'objectcache', '*', __METHOD__ ); |
| 393 | + $db->commit(); |
| 394 | + } catch ( DBQueryError $e ) { |
| 395 | + $this->handleWriteError( $e ); |
391 | 396 | } |
392 | | - $this->_begin(); |
393 | | - $this->_query( "DELETE FROM $0" ); |
394 | | - $this->_commit(); |
395 | 397 | } |
396 | 398 | |
397 | 399 | /** |
— | — | @@ -401,9 +403,9 @@ |
402 | 404 | * @param $data mixed |
403 | 405 | * @return string |
404 | 406 | */ |
405 | | - function _serialize( &$data ) { |
| 407 | + protected function serialize( &$data ) { |
406 | 408 | $serial = serialize( $data ); |
407 | | - if( function_exists( 'gzdeflate' ) ) { |
| 409 | + if ( function_exists( 'gzdeflate' ) ) { |
408 | 410 | return gzdeflate( $serial ); |
409 | 411 | } else { |
410 | 412 | return $serial; |
— | — | @@ -415,104 +417,41 @@ |
416 | 418 | * @param $serial string |
417 | 419 | * @return mixed |
418 | 420 | */ |
419 | | - function _unserialize( $serial ) { |
420 | | - if( function_exists( 'gzinflate' ) ) { |
| 421 | + protected function unserialize( $serial ) { |
| 422 | + if ( function_exists( 'gzinflate' ) ) { |
421 | 423 | $decomp = @gzinflate( $serial ); |
422 | | - if( false !== $decomp ) { |
| 424 | + if ( false !== $decomp ) { |
423 | 425 | $serial = $decomp; |
424 | 426 | } |
425 | 427 | } |
426 | 428 | $ret = unserialize( $serial ); |
427 | 429 | return $ret; |
428 | 430 | } |
429 | | -} |
430 | 431 | |
431 | | -/** |
432 | | - * Stores objects in the main database of the wiki |
433 | | - * |
434 | | - * @ingroup Cache |
435 | | - */ |
436 | | -class MediaWikiBagOStuff extends SqlBagOStuff { |
437 | | - var $tableInitialised = false; |
438 | | - var $lb, $db; |
439 | | - |
440 | | - function _getDB(){ |
441 | | - if ( !isset( $this->lb ) ) { |
442 | | - $this->lb = wfGetLBFactory()->newMainLB(); |
443 | | - $this->db = $this->lb->getConnection( DB_MASTER ); |
444 | | - $this->db->clearFlag( DBO_TRX ); |
| 432 | + /** |
| 433 | + * Handle a DBQueryError which occurred during a write operation. |
| 434 | + * Ignore errors which are due to a read-only database, rethrow others. |
| 435 | + */ |
| 436 | + protected function handleWriteError( $exception ) { |
| 437 | + $db = $this->getDB(); |
| 438 | + if ( !$db->wasReadOnlyError() ) { |
| 439 | + throw $exception; |
445 | 440 | } |
446 | | - return $this->db; |
447 | | - } |
448 | | - function _begin() { |
449 | | - $this->_getDB()->begin(); |
450 | | - } |
451 | | - function _commit() { |
452 | | - $this->_getDB()->commit(); |
453 | | - } |
454 | | - function _doquery($sql) { |
455 | | - return $this->_getDB()->query( $sql, __METHOD__ ); |
456 | | - } |
457 | | - function _doinsert($t, $v) { |
458 | | - return $this->_getDB()->insert($t, $v, __METHOD__, array( 'IGNORE' ) ); |
459 | | - } |
460 | | - function _fetchobject($result) { |
461 | | - return $this->_getDB()->fetchObject($result); |
462 | | - } |
463 | | - function _freeresult($result) { |
464 | | - return $this->_getDB()->freeResult($result); |
465 | | - } |
466 | | - function _dberror($result) { |
467 | | - return $this->_getDB()->lastError(); |
468 | | - } |
469 | | - function _maxdatetime() { |
470 | | - if ( time() > 0x7fffffff ) { |
471 | | - return $this->_fromunixtime( 1<<62 ); |
472 | | - } else { |
473 | | - return $this->_fromunixtime( 0x7fffffff ); |
| 441 | + try { |
| 442 | + $db->rollback(); |
| 443 | + } catch ( DBQueryError $e ) { |
474 | 444 | } |
| 445 | + wfDebug( __METHOD__ . ": ignoring query error\n" ); |
| 446 | + $db->ignoreErrors( false ); |
475 | 447 | } |
476 | | - function _fromunixtime($ts) { |
477 | | - return $this->_getDB()->timestamp($ts); |
478 | | - } |
479 | | - /*** |
480 | | - * Note -- this should *not* check wfReadOnly(). |
481 | | - * Read-only mode has been repurposed from the original |
482 | | - * "nothing must write to the database" to "users should not |
483 | | - * be able to edit or alter anything user-visible". |
484 | | - * |
485 | | - * Backend bits like the object cache should continue |
486 | | - * to work in this mode, otherwise things will blow up |
487 | | - * like the message cache failing to save its state, |
488 | | - * causing long delays (bug 11533). |
489 | | - */ |
490 | | - function _readonly(){ |
491 | | - return false; |
492 | | - } |
493 | | - function _strencode($s) { |
494 | | - return $this->_getDB()->strencode($s); |
495 | | - } |
496 | | - function _blobencode($s) { |
497 | | - return $this->_getDB()->encodeBlob($s); |
498 | | - } |
499 | | - function _blobdecode($s) { |
500 | | - return $this->_getDB()->decodeBlob($s); |
501 | | - } |
502 | | - function getTableName() { |
503 | | - if ( !$this->tableInitialised ) { |
504 | | - $dbw = $this->_getDB(); |
505 | | - /* This is actually a hack, we should be able |
506 | | - to use Language classes here... or not */ |
507 | | - if (!$dbw) |
508 | | - throw new MWException("Could not connect to database"); |
509 | | - $this->table = $dbw->tableName( $this->table ); |
510 | | - $this->tableInitialised = true; |
511 | | - } |
512 | | - return $this->table; |
513 | | - } |
514 | 448 | } |
515 | 449 | |
516 | 450 | /** |
| 451 | + * Backwards compatibility alias |
| 452 | + */ |
| 453 | +class MediaWikiBagOStuff extends SqlBagOStuff {} |
| 454 | + |
| 455 | +/** |
517 | 456 | * This is a wrapper for Turck MMCache's shared memory functions. |
518 | 457 | * |
519 | 458 | * You can store objects with mmcache_put() and mmcache_get(), but Turck seems |
— | — | @@ -528,7 +467,7 @@ |
529 | 468 | * @ingroup Cache |
530 | 469 | */ |
531 | 470 | class TurckBagOStuff extends BagOStuff { |
532 | | - function get($key) { |
| 471 | + public function get( $key ) { |
533 | 472 | $val = mmcache_get( $key ); |
534 | 473 | if ( is_string( $val ) ) { |
535 | 474 | $val = unserialize( $val ); |
— | — | @@ -536,22 +475,22 @@ |
537 | 476 | return $val; |
538 | 477 | } |
539 | 478 | |
540 | | - function set($key, $value, $exptime=0) { |
| 479 | + public function set( $key, $value, $exptime = 0 ) { |
541 | 480 | mmcache_put( $key, serialize( $value ), $exptime ); |
542 | 481 | return true; |
543 | 482 | } |
544 | 483 | |
545 | | - function delete($key, $time=0) { |
| 484 | + public function delete( $key, $time = 0 ) { |
546 | 485 | mmcache_rm( $key ); |
547 | 486 | return true; |
548 | 487 | } |
549 | 488 | |
550 | | - function lock($key, $waitTimeout = 0 ) { |
| 489 | + public function lock( $key, $waitTimeout = 0 ) { |
551 | 490 | mmcache_lock( $key ); |
552 | 491 | return true; |
553 | 492 | } |
554 | 493 | |
555 | | - function unlock($key) { |
| 494 | + public function unlock( $key ) { |
556 | 495 | mmcache_unlock( $key ); |
557 | 496 | return true; |
558 | 497 | } |
— | — | @@ -563,21 +502,21 @@ |
564 | 503 | * @ingroup Cache |
565 | 504 | */ |
566 | 505 | class APCBagOStuff extends BagOStuff { |
567 | | - function get($key) { |
568 | | - $val = apc_fetch($key); |
| 506 | + public function get( $key ) { |
| 507 | + $val = apc_fetch( $key ); |
569 | 508 | if ( is_string( $val ) ) { |
570 | 509 | $val = unserialize( $val ); |
571 | 510 | } |
572 | 511 | return $val; |
573 | 512 | } |
574 | 513 | |
575 | | - function set($key, $value, $exptime=0) { |
576 | | - apc_store($key, serialize($value), $exptime); |
| 514 | + public function set( $key, $value, $exptime = 0 ) { |
| 515 | + apc_store( $key, serialize( $value ), $exptime ); |
577 | 516 | return true; |
578 | 517 | } |
579 | 518 | |
580 | | - function delete($key, $time=0) { |
581 | | - apc_delete($key); |
| 519 | + public function delete( $key, $time = 0 ) { |
| 520 | + apc_delete( $key ); |
582 | 521 | return true; |
583 | 522 | } |
584 | 523 | } |
— | — | @@ -592,7 +531,7 @@ |
593 | 532 | * @ingroup Cache |
594 | 533 | */ |
595 | 534 | class eAccelBagOStuff extends BagOStuff { |
596 | | - function get($key) { |
| 535 | + public function get( $key ) { |
597 | 536 | $val = eaccelerator_get( $key ); |
598 | 537 | if ( is_string( $val ) ) { |
599 | 538 | $val = unserialize( $val ); |
— | — | @@ -600,22 +539,22 @@ |
601 | 540 | return $val; |
602 | 541 | } |
603 | 542 | |
604 | | - function set($key, $value, $exptime=0) { |
| 543 | + public function set( $key, $value, $exptime = 0 ) { |
605 | 544 | eaccelerator_put( $key, serialize( $value ), $exptime ); |
606 | 545 | return true; |
607 | 546 | } |
608 | 547 | |
609 | | - function delete($key, $time=0) { |
| 548 | + public function delete( $key, $time = 0 ) { |
610 | 549 | eaccelerator_rm( $key ); |
611 | 550 | return true; |
612 | 551 | } |
613 | 552 | |
614 | | - function lock($key, $waitTimeout = 0 ) { |
| 553 | + public function lock( $key, $waitTimeout = 0 ) { |
615 | 554 | eaccelerator_lock( $key ); |
616 | 555 | return true; |
617 | 556 | } |
618 | 557 | |
619 | | - function unlock($key) { |
| 558 | + public function unlock( $key ) { |
620 | 559 | eaccelerator_unlock( $key ); |
621 | 560 | return true; |
622 | 561 | } |
— | — | @@ -637,7 +576,7 @@ |
638 | 577 | */ |
639 | 578 | public function get( $key ) { |
640 | 579 | $val = xcache_get( $key ); |
641 | | - if( is_string( $val ) ) |
| 580 | + if ( is_string( $val ) ) |
642 | 581 | $val = unserialize( $val ); |
643 | 582 | return $val; |
644 | 583 | } |
— | — | @@ -670,20 +609,24 @@ |
671 | 610 | } |
672 | 611 | |
673 | 612 | /** |
674 | | - * @todo document |
| 613 | + * Cache that uses DBA as a backend. |
| 614 | + * Slow due to the need to constantly open and close the file to avoid holding |
| 615 | + * writer locks. Intended for development use only, as a memcached workalike |
| 616 | + * for systems that don't have it. |
| 617 | + * |
675 | 618 | * @ingroup Cache |
676 | 619 | */ |
677 | 620 | class DBABagOStuff extends BagOStuff { |
678 | 621 | var $mHandler, $mFile, $mReader, $mWriter, $mDisabled; |
679 | 622 | |
680 | | - function __construct( $handler = 'db3', $dir = false ) { |
| 623 | + public function __construct( $handler = 'db3', $dir = false ) { |
681 | 624 | if ( $dir === false ) { |
682 | 625 | global $wgTmpDirectory; |
683 | 626 | $dir = $wgTmpDirectory; |
684 | 627 | } |
685 | 628 | $this->mFile = "$dir/mw-cache-" . wfWikiID(); |
686 | 629 | $this->mFile .= '.db'; |
687 | | - wfDebug( __CLASS__.": using cache file {$this->mFile}\n" ); |
| 630 | + wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" ); |
688 | 631 | $this->mHandler = $handler; |
689 | 632 | } |
690 | 633 | |
— | — | @@ -692,7 +635,7 @@ |
693 | 636 | */ |
694 | 637 | function encode( $value, $expiry ) { |
695 | 638 | # Convert to absolute time |
696 | | - $expiry = BagOStuff::convertExpiry( $expiry ); |
| 639 | + $expiry = $this->convertExpiry( $expiry ); |
697 | 640 | return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value ); |
698 | 641 | } |
699 | 642 | |
— | — | @@ -732,7 +675,7 @@ |
733 | 676 | |
734 | 677 | function get( $key ) { |
735 | 678 | wfProfileIn( __METHOD__ ); |
736 | | - wfDebug( __METHOD__."($key)\n" ); |
| 679 | + wfDebug( __METHOD__ . "($key)\n" ); |
737 | 680 | $handle = $this->getReader(); |
738 | 681 | if ( !$handle ) { |
739 | 682 | return null; |
— | — | @@ -747,16 +690,16 @@ |
748 | 691 | $handle = $this->getWriter(); |
749 | 692 | dba_delete( $key, $handle ); |
750 | 693 | dba_close( $handle ); |
751 | | - wfDebug( __METHOD__.": $key expired\n" ); |
| 694 | + wfDebug( __METHOD__ . ": $key expired\n" ); |
752 | 695 | $val = null; |
753 | 696 | } |
754 | 697 | wfProfileOut( __METHOD__ ); |
755 | 698 | return $val; |
756 | 699 | } |
757 | 700 | |
758 | | - function set( $key, $value, $exptime=0 ) { |
| 701 | + function set( $key, $value, $exptime = 0 ) { |
759 | 702 | wfProfileIn( __METHOD__ ); |
760 | | - wfDebug( __METHOD__."($key)\n" ); |
| 703 | + wfDebug( __METHOD__ . "($key)\n" ); |
761 | 704 | $blob = $this->encode( $value, $exptime ); |
762 | 705 | $handle = $this->getWriter(); |
763 | 706 | if ( !$handle ) { |
— | — | @@ -770,7 +713,7 @@ |
771 | 714 | |
772 | 715 | function delete( $key, $time = 0 ) { |
773 | 716 | wfProfileIn( __METHOD__ ); |
774 | | - wfDebug( __METHOD__."($key)\n" ); |
| 717 | + wfDebug( __METHOD__ . "($key)\n" ); |
775 | 718 | $handle = $this->getWriter(); |
776 | 719 | if ( !$handle ) { |
777 | 720 | return false; |
— | — | @@ -808,11 +751,11 @@ |
809 | 752 | function keys() { |
810 | 753 | $reader = $this->getReader(); |
811 | 754 | $k1 = dba_firstkey( $reader ); |
812 | | - if( !$k1 ) { |
| 755 | + if ( !$k1 ) { |
813 | 756 | return array(); |
814 | 757 | } |
815 | 758 | $result[] = $k1; |
816 | | - while( $key = dba_nextkey( $reader ) ) { |
| 759 | + while ( $key = dba_nextkey( $reader ) ) { |
817 | 760 | $result[] = $key; |
818 | 761 | } |
819 | 762 | return $result; |
Index: trunk/phase3/includes/Article.php |
— | — | @@ -3080,6 +3080,9 @@ |
3081 | 3081 | */ |
3082 | 3082 | public function viewUpdates() { |
3083 | 3083 | global $wgDeferredUpdateList, $wgDisableCounters, $wgUser; |
| 3084 | + if ( wfReadOnly() ) { |
| 3085 | + return; |
| 3086 | + } |
3084 | 3087 | # Don't update page view counters on views from bot users (bug 14044) |
3085 | 3088 | if( !$wgDisableCounters && !$wgUser->isAllowed('bot') && $this->getID() ) { |
3086 | 3089 | Article::incViewCount( $this->getID() ); |
Index: trunk/phase3/includes/ObjectCache.php |
— | — | @@ -91,7 +91,7 @@ |
92 | 92 | |
93 | 93 | if ( $type == CACHE_DB || ( $inputType == CACHE_ANYTHING && $cache === false ) ) { |
94 | 94 | if ( !array_key_exists( CACHE_DB, $wgCaches ) ) { |
95 | | - $wgCaches[CACHE_DB] = new MediaWikiBagOStuff('objectcache'); |
| 95 | + $wgCaches[CACHE_DB] = new SqlBagOStuff('objectcache'); |
96 | 96 | } |
97 | 97 | $cache =& $wgCaches[CACHE_DB]; |
98 | 98 | } |
Index: trunk/phase3/includes/LocalisationCache.php |
— | — | @@ -723,6 +723,7 @@ |
724 | 724 | var $currentLang; |
725 | 725 | var $writesDone = false; |
726 | 726 | var $dbw, $batch; |
| 727 | + var $readOnly = false; |
727 | 728 | |
728 | 729 | public function get( $code, $key ) { |
729 | 730 | if ( $this->writesDone ) { |
— | — | @@ -740,17 +741,34 @@ |
741 | 742 | } |
742 | 743 | |
743 | 744 | public function startWrite( $code ) { |
| 745 | + if ( $this->readOnly ) { |
| 746 | + return; |
| 747 | + } |
744 | 748 | if ( !$code ) { |
745 | 749 | throw new MWException( __METHOD__.": Invalid language \"$code\"" ); |
746 | 750 | } |
747 | 751 | $this->dbw = wfGetDB( DB_MASTER ); |
748 | | - $this->dbw->begin(); |
749 | | - $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ ); |
| 752 | + try { |
| 753 | + $this->dbw->begin(); |
| 754 | + $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ ); |
| 755 | + } catch ( DBQueryError $e ) { |
| 756 | + if ( $this->dbw->wasReadOnlyError() ) { |
| 757 | + $this->readOnly = true; |
| 758 | + $this->dbw->rollback(); |
| 759 | + $this->dbw->ignoreErrors( false ); |
| 760 | + return; |
| 761 | + } else { |
| 762 | + throw $e; |
| 763 | + } |
| 764 | + } |
750 | 765 | $this->currentLang = $code; |
751 | 766 | $this->batch = array(); |
752 | 767 | } |
753 | 768 | |
754 | 769 | public function finishWrite() { |
| 770 | + if ( $this->readOnly ) { |
| 771 | + return; |
| 772 | + } |
755 | 773 | if ( $this->batch ) { |
756 | 774 | $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); |
757 | 775 | } |
— | — | @@ -762,6 +780,9 @@ |
763 | 781 | } |
764 | 782 | |
765 | 783 | public function set( $key, $value ) { |
| 784 | + if ( $this->readOnly ) { |
| 785 | + return; |
| 786 | + } |
766 | 787 | if ( is_null( $this->currentLang ) ) { |
767 | 788 | throw new MWException( __CLASS__.': must call startWrite() before calling set()' ); |
768 | 789 | } |
Index: trunk/phase3/includes/MessageCache.php |
— | — | @@ -432,16 +432,10 @@ |
433 | 433 | |
434 | 434 | $cacheKey = wfMemcKey( 'messages', $code ); |
435 | 435 | |
436 | | - $i = 0; |
437 | 436 | if ( $memc ) { |
438 | | - # Save in memcached |
439 | | - # Keep trying if it fails, this is kind of important |
440 | | - |
441 | | - for ($i=0; $i<20 && |
442 | | - !$this->mMemc->set( $cacheKey, $cache, $this->mExpiry ); |
443 | | - $i++ ) { |
444 | | - usleep(mt_rand(500000,1500000)); |
445 | | - } |
| 437 | + $success = $this->mMemc->set( $cacheKey, $cache, $this->mExpiry ); |
| 438 | + } else { |
| 439 | + $success = true; |
446 | 440 | } |
447 | 441 | |
448 | 442 | # Save to local cache |
— | — | @@ -456,11 +450,6 @@ |
457 | 451 | } |
458 | 452 | } |
459 | 453 | |
460 | | - if ( $i == 20 ) { |
461 | | - $success = false; |
462 | | - } else { |
463 | | - $success = true; |
464 | | - } |
465 | 454 | wfProfileOut( __METHOD__ ); |
466 | 455 | return $success; |
467 | 456 | } |
Index: trunk/phase3/includes/db/DatabaseMysql.php |
— | — | @@ -360,6 +360,31 @@ |
361 | 361 | $encValue = $value ? '1' : '0'; |
362 | 362 | $this->query( "SET sql_big_selects=$encValue", __METHOD__ ); |
363 | 363 | } |
| 364 | + |
| 365 | + |
| 366 | + /** |
| 367 | + * Determines if the last failure was due to a deadlock |
| 368 | + */ |
| 369 | + function wasDeadlock() { |
| 370 | + return $this->lastErrno() == 1213; |
| 371 | + } |
| 372 | + |
| 373 | + /** |
| 374 | + * Determines if the last query error was something that should be dealt |
| 375 | + * with by pinging the connection and reissuing the query |
| 376 | + */ |
| 377 | + function wasErrorReissuable() { |
| 378 | + return $this->lastErrno() == 2013 || $this->lastErrno() == 2006; |
| 379 | + } |
| 380 | + |
| 381 | + /** |
| 382 | + * Determines if the last failure was due to the database being read-only. |
| 383 | + */ |
| 384 | + function wasReadOnlyError() { |
| 385 | + return $this->lastErrno() == 1223 || |
| 386 | + ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false ); |
| 387 | + } |
| 388 | + |
364 | 389 | } |
365 | 390 | |
366 | 391 | /** |
Index: trunk/phase3/includes/db/Database.php |
— | — | @@ -1715,20 +1715,30 @@ |
1716 | 1716 | |
1717 | 1717 | /** |
1718 | 1718 | * Determines if the last failure was due to a deadlock |
| 1719 | + * STUB |
1719 | 1720 | */ |
1720 | 1721 | function wasDeadlock() { |
1721 | | - return $this->lastErrno() == 1213; |
| 1722 | + return false; |
1722 | 1723 | } |
1723 | 1724 | |
1724 | 1725 | /** |
1725 | 1726 | * Determines if the last query error was something that should be dealt |
1726 | | - * with by pinging the connection and reissuing the query |
| 1727 | + * with by pinging the connection and reissuing the query. |
| 1728 | + * STUB |
1727 | 1729 | */ |
1728 | 1730 | function wasErrorReissuable() { |
1729 | | - return $this->lastErrno() == 2013 || $this->lastErrno() == 2006; |
| 1731 | + return false; |
1730 | 1732 | } |
1731 | 1733 | |
1732 | 1734 | /** |
| 1735 | + * Determines if the last failure was due to the database being read-only. |
| 1736 | + * STUB |
| 1737 | + */ |
| 1738 | + function wasReadOnlyError() { |
| 1739 | + return false; |
| 1740 | + } |
| 1741 | + |
| 1742 | + /** |
1733 | 1743 | * Perform a deadlock-prone transaction. |
1734 | 1744 | * |
1735 | 1745 | * This function invokes a callback function to perform a set of write |
Index: trunk/phase3/includes/db/DatabaseSqlite.php |
— | — | @@ -280,6 +280,10 @@ |
281 | 281 | return $this->lastErrno() == SQLITE_SCHEMA; |
282 | 282 | } |
283 | 283 | |
| 284 | + function wasReadOnlyError() { |
| 285 | + return $this->lastErrno() == SQLITE_READONLY; |
| 286 | + } |
| 287 | + |
284 | 288 | /** |
285 | 289 | * @return string wikitext of a link to the server software's web site |
286 | 290 | */ |