Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php |
— | — | @@ -1,7 +1,5 @@ |
2 | 2 | <?php |
3 | 3 | /** |
4 | | - * Class for a "backend" consisting of a orioritized list of backend |
5 | | - * |
6 | 4 | * @file |
7 | 5 | * @ingroup FileRepo |
8 | 6 | */ |
— | — | @@ -41,35 +39,56 @@ |
42 | 40 | |
43 | 41 | final public function doOperations( array $ops ) { |
44 | 42 | $status = Status::newGood(); |
| 43 | + |
45 | 44 | // Build up a list of FileOps. The list will have all the ops |
46 | 45 | // for one backend, then all the ops for the next, and so on. |
| 46 | + // Also build up a list of files to lock... |
47 | 47 | $performOps = array(); |
48 | | - foreach ( $this->fileBackends as $backend ) { |
| 48 | + $filesToLock = array(); |
| 49 | + foreach ( $this->fileBackends as $index => $backend ) { |
49 | 50 | $performOps = array_merge( $performOps, $backend->getOperations( $ops ) ); |
| 51 | + // Set $filesToLock from the first backend so we don't try to set all |
| 52 | + // locks two or three times (depending on the number of backends). |
| 53 | + if ( $index == 0 ) { |
| 54 | + foreach ( $performOps as $index => $fileOp ) { |
| 55 | + $filesToLock = array_merge( $filesToLock, $fileOp->storagePathsToLock() ); |
| 56 | + } |
| 57 | + $filesToLock = array_unique( $filesToLock ); // avoid warnings |
| 58 | + } |
50 | 59 | } |
| 60 | + |
| 61 | + // Try to lock those files... |
| 62 | + $status->merge( $this->lockFiles( $filesToLock ) ); |
| 63 | + if ( !$status->isOK() ) { |
| 64 | + return $status; // abort |
| 65 | + } |
| 66 | + |
51 | 67 | // Attempt each operation; abort on failure... |
52 | | - foreach ( $performOps as $index => $transaction ) { |
53 | | - $subStatus = $transaction->attempt(); |
54 | | - $status->merge( $subStatus ); |
55 | | - if ( !$subStatus->isOK() ) { // operation failed? |
| 68 | + foreach ( $performOps as $index => $fileOp ) { |
| 69 | + $status->merge( $fileOp->attempt() ); |
| 70 | + if ( !$status->isOK() ) { // operation failed? |
56 | 71 | // Revert everything done so far and abort. |
57 | 72 | // Do this by reverting each previous operation in reverse order. |
58 | 73 | $pos = $index - 1; // last one failed; no need to revert() |
59 | 74 | while ( $pos >= 0 ) { |
60 | | - $subStatus = $performOps[$pos]->revert(); |
61 | | - $status->merge( $subStatus ); |
| 75 | + $status->merge( $performOps[$pos]->revert() ); |
62 | 76 | $pos--; |
63 | 77 | } |
64 | 78 | return $status; |
65 | 79 | } |
66 | 80 | } |
| 81 | + |
67 | 82 | // Finish each operation... |
68 | | - foreach ( $performOps as $index => $transaction ) { |
69 | | - $subStatus = $transaction->finish(); |
70 | | - $status->merge( $subStatus ); |
| 83 | + foreach ( $performOps as $index => $fileOp ) { |
| 84 | + $status->merge( $fileOp->finish() ); |
71 | 85 | } |
72 | | - // Make sure status is OK, despite any finish() fatals |
| 86 | + |
| 87 | + // Unlock all the files |
| 88 | + $status->merge( $this->unlockFiles( $filesToLock ) ); |
| 89 | + |
| 90 | + // Make sure status is OK, despite any finish() or unlockFiles() fatals |
73 | 91 | $status->setResult( true ); |
| 92 | + |
74 | 93 | return $status; |
75 | 94 | } |
76 | 95 | |
— | — | @@ -145,13 +164,11 @@ |
146 | 165 | function lockFiles( array $paths ) { |
147 | 166 | $status = Status::newGood(); |
148 | 167 | foreach ( $this->backends as $index => $backend ) { |
149 | | - $subStatus = $backend->lockFiles( $paths ); |
150 | | - $status->merge( $subStatus ); |
151 | | - if ( !$subStatus->isOk() ) { |
| 168 | + $status->merge( $backend->lockFiles( $paths ) ); |
| 169 | + if ( !$status->isOk() ) { |
152 | 170 | // Lock failed: release the locks done so far each backend |
153 | 171 | for ( $i=0; $i < $index; $i++ ) { // don't do backend $index since it failed |
154 | | - $subStatus = $backend->unlockFiles( $paths ); |
155 | | - $status->merge( $subStatus ); |
| 172 | + $status->merge( $backend->unlockFiles( $paths ) ); |
156 | 173 | } |
157 | 174 | return $status; |
158 | 175 | } |
— | — | @@ -162,8 +179,7 @@ |
163 | 180 | function unlockFiles( array $paths ) { |
164 | 181 | $status = Status::newGood(); |
165 | 182 | foreach ( $this->backends as $backend ) { |
166 | | - $subStatus = $backend->unlockFile( $paths ); |
167 | | - $status->merge( $subStatus ); |
| 183 | + $status->merge( $backend->unlockFile( $paths ) ); |
168 | 184 | } |
169 | 185 | return $status; |
170 | 186 | } |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileLockManager.php |
— | — | @@ -97,8 +97,7 @@ |
98 | 98 | $lockedKeys[] = $key; |
99 | 99 | } else { |
100 | 100 | // Abort and unlock everything |
101 | | - $subStatus = $this->doUnlock( $lockedKeys ); |
102 | | - $status->merge( $subStatus ); |
| 101 | + $status->merge( $this->doUnlock( $lockedKeys ) ); |
103 | 102 | return $status; |
104 | 103 | } |
105 | 104 | } |
— | — | @@ -110,8 +109,7 @@ |
111 | 110 | $status = Status::newGood(); |
112 | 111 | |
113 | 112 | foreach ( $keys as $key ) { |
114 | | - $subStatus = $this->doSingleUnlock( $key ); |
115 | | - $status->merge( $subStatus ); |
| 113 | + $status->merge( $this->doSingleUnlock( $key ) ); |
116 | 114 | } |
117 | 115 | |
118 | 116 | return $status; |
— | — | @@ -181,21 +179,19 @@ |
182 | 180 | * One or several lock database servers are set up having a `file_locking` |
183 | 181 | * table with one field, fl_key, the PRIMARY KEY. The table engine should |
184 | 182 | * have row-level locking. For performance, deadlock detection should be |
185 | | - * disabled and a low lock-wait timeout should be put in the server config. |
| 183 | + * disabled and a low lock-wait timeout should be set via server config. |
186 | 184 | * |
187 | 185 | * All lock requests for an item (identified by an abstract key string) will |
188 | | - * map to one bucket. Each bucket maps to a single server, and each server can |
189 | | - * have several or no fallback servers. Fallback servers recieve the same lock |
190 | | - * statements as the servers they are set as fallbacks for. This propagation is |
191 | | - * only best-effort; lock requests will not be blocked just because a fallback |
192 | | - * server cannot be contacted to recieve a copy of the lock request. |
| 186 | + * map to one bucket. Each bucket maps to a single server, though each server |
| 187 | + * can have several fallback servers. |
| 188 | + * |
| 189 | + * Fallback servers recieve the same lock statements as the servers they standby for. |
| 190 | + * This propagation is only best-effort; lock requests will not be blocked just |
| 191 | + * because a fallback server cannot recieve a copy of the lock request. |
193 | 192 | */ |
194 | 193 | class DBFileLockManager extends FileLockManager { |
195 | 194 | /** @var Array Map of bucket indexes to server names */ |
196 | | - protected $serverMap = array(); // (index => server name) |
197 | | - /** @var Array Map of servers to fallback server names */ |
198 | | - protected $serverFallbackMap = array(); // (server => (server1,server2,...)) |
199 | | - |
| 195 | + protected $serverMap = array(); // (index => (server1,server2,...)) |
200 | 196 | /** @var Array List of active lock key names */ |
201 | 197 | protected $locksHeld = array(); // (key => 1) |
202 | 198 | /** $var Array Map Lock-active database connections (name => Database) */ |
— | — | @@ -203,54 +199,64 @@ |
204 | 200 | |
205 | 201 | /** |
206 | 202 | * Construct a new instance from configuration. |
207 | | - * The 'serverMap' param of $config has is an array of consecutive |
208 | | - * integer keys, starting from 0, with server name strings as values. |
209 | | - * It should have no more than 16 items in the array. |
210 | | - * |
211 | | - * The `file_locking` table should have row-level locking (e.g. innoDB). |
212 | | - * |
213 | | - * @param array $config |
| 203 | + * $config paramaters include: |
| 204 | + * 'serverMap' : Array of no more than 16 consecutive integer keys, |
| 205 | + * starting from 0, with a list of servers as values. |
| 206 | + * The first server in each list is the main server and |
| 207 | + * the others are fallback servers. |
| 208 | + * |
| 209 | + * @param Array $config |
214 | 210 | */ |
215 | 211 | function __construct( array $config ) { |
216 | | - $this->serverMap = $config['serverMap']; |
217 | | - $this->serverFallbackMap = $config['serverFallbackMap']; |
| 212 | + $this->serverMap = (array)$config['serverMap']; |
| 213 | + // Sanitize serverMap against bad config to prevent PHP errors |
| 214 | + for ( $i=0; $i < count( $this->serverMap ); $i++ ) { |
| 215 | + if ( |
| 216 | + !isset( $this->serverMap[$i] ) || // not consecutive |
| 217 | + !is_array( $this->serverMap[$i] ) || // bad type |
| 218 | + !count( $this->serverMap[$i] ) // empty list |
| 219 | + ) { |
| 220 | + $this->serverMap[$i] = null; // see getBucketFromKey() |
| 221 | + wfWarn( "No key for bucket $i in serverMap or server list is empty." ); |
| 222 | + } |
| 223 | + } |
218 | 224 | } |
219 | 225 | |
220 | 226 | function doLock( array $keys ) { |
221 | 227 | $status = Status::newGood(); |
222 | 228 | |
223 | 229 | $keysToLock = array(); |
224 | | - // Get locks that need to be acquired and which server they map to... |
| 230 | + // Get locks that need to be acquired (buckets => locks)... |
225 | 231 | foreach ( $keys as $key ) { |
226 | 232 | if ( isset( $this->locksHeld[$key] ) ) { |
227 | 233 | $status->warning( 'File already locked.' ); |
228 | 234 | } else { |
229 | | - $server = $this->getDBServerFromKey( $key ); |
230 | | - if ( $server === null ) { // config error? |
231 | | - $status->fatal( "Lock server for $key is not set." ); |
| 235 | + $bucket = $this->getBucketFromKey( $key ); |
| 236 | + if ( $bucket === null ) { // config error? |
| 237 | + $status->fatal( "Lock servers for key $key is not set." ); |
232 | 238 | return $status; |
233 | 239 | } |
234 | | - $keysToLock[$server][] = $key; |
| 240 | + $keysToLock[$bucket][] = $key; |
235 | 241 | } |
236 | 242 | } |
237 | 243 | |
238 | 244 | $lockedKeys = array(); // files locked in this attempt |
239 | 245 | // Attempt to acquire these locks... |
240 | | - foreach ( $keysToLock as $server => $keys ) { |
| 246 | + foreach ( $keysToLock as $bucket => $keys ) { |
| 247 | + $server = $this->serverMap[$bucket][0]; // primary lock server |
| 248 | + $propagateToFallbacks = true; // give lock statements to fallback servers |
241 | 249 | // Acquire the locks for this server. Three main cases can happen: |
242 | 250 | // (a) Server is up; common case |
243 | 251 | // (b) Server is down but a fallback is up |
244 | 252 | // (c) Server is down and no fallbacks are up (or none defined) |
245 | | - $propagateToFallbacks = true; |
246 | 253 | try { |
247 | 254 | $this->lockingSelect( $server, $keys ); // SELECT FOR UPDATE |
248 | 255 | } catch ( DBError $e ) { |
249 | 256 | // Can we manage to lock on any of the fallback servers? |
250 | | - if ( !$this->lockingSelectFallbacks( $server, $keys ) ) { |
251 | | - // Abort and unlock everything |
| 257 | + if ( !$this->lockingSelectFallbacks( $bucket, $keys ) ) { |
| 258 | + // Abort and unlock everything we just locked |
252 | 259 | $status->fatal( "Could not contact the lock server." ); |
253 | | - $subStatus = $this->doUnlock( $lockedKeys ); |
254 | | - $status->merge( $subStatus ); |
| 260 | + $status->merge( $this->doUnlock( $lockedKeys ) ); |
255 | 261 | return $status; |
256 | 262 | } else { // recovered using fallbacks |
257 | 263 | $propagateToFallbacks = false; // done already |
— | — | @@ -258,7 +264,7 @@ |
259 | 265 | } |
260 | 266 | // Propagate any locks to the fallback servers (best effort) |
261 | 267 | if ( $propagateToFallbacks ) { |
262 | | - $this->lockingSelectFallbacks( $server, $keys ); |
| 268 | + $this->lockingSelectFallbacks( $bucket, $keys ); |
263 | 269 | } |
264 | 270 | // Record locks as active |
265 | 271 | foreach ( $keys as $key ) { |
— | — | @@ -290,7 +296,7 @@ |
291 | 297 | } |
292 | 298 | |
293 | 299 | /** |
294 | | - * Get a database connection for $server and lock the rows for $keys |
| 300 | + * Get a DB connection to a lock server and acquire locks on $keys. |
295 | 301 | * |
296 | 302 | * @param $server string |
297 | 303 | * @param $keys Array |
— | — | @@ -311,24 +317,23 @@ |
312 | 318 | } |
313 | 319 | |
314 | 320 | /** |
315 | | - * Propagate any locks to the fallback servers for $server. |
| 321 | + * Propagate any locks to the fallback servers for a bucket. |
316 | 322 | * This should avoid throwing any exceptions. |
317 | 323 | * |
318 | | - * @param $server string |
| 324 | + * @param $bucket integer |
319 | 325 | * @param $keys Array |
320 | 326 | * @return bool Locks made on at least one fallback server |
321 | 327 | */ |
322 | | - protected function lockingSelectFallbacks( $server, array $keys ) { |
| 328 | + protected function lockingSelectFallbacks( $bucket, array $keys ) { |
323 | 329 | $locksMade = false; |
324 | | - if ( isset( $this->serverFallbackMap[$server] ) ) { |
325 | | - // Propagate the $server locks to each fallback for $server... |
326 | | - foreach ( $this->serverFallbackMap[$server] as $fallbackServer ) { |
327 | | - try { |
328 | | - $this->doLockingSelect( $fallbackServer, $keys ); // SELECT FOR UPDATE |
329 | | - $locksMade = true; |
330 | | - } catch ( DBError $e ) { |
331 | | - // oh well; best effort |
332 | | - } |
| 330 | + $count = count( $this->serverMap[$bucket] ); |
| 331 | + for ( $i=1; $i < $count; $i++ ) { // start at 1 to only include fallbacks |
| 332 | + $server = $this->serverMap[$bucket][$i]; |
| 333 | + try { |
| 334 | + $this->doLockingSelect( $server, $keys ); // SELECT FOR UPDATE |
| 335 | + $locksMade = true; // success for this fallback |
| 336 | + } catch ( DBError $e ) { |
| 337 | + // oh well; best effort |
333 | 338 | } |
334 | 339 | } |
335 | 340 | return $locksMade; |
— | — | @@ -341,14 +346,14 @@ |
342 | 347 | * @return void |
343 | 348 | */ |
344 | 349 | protected function commitLockTransactions() { |
345 | | - try { |
346 | | - foreach ( $this->activeConns as $server => $db ) { |
| 350 | + foreach ( $this->activeConns as $server => $db ) { |
| 351 | + try { |
347 | 352 | $db->commit(); // finish transaction |
348 | | - unset( $this->activeConns[$server] ); |
| 353 | + } catch ( DBError $e ) { |
| 354 | + // oh well |
349 | 355 | } |
350 | | - } catch ( DBError $e ) { |
351 | | - // oh well |
352 | 356 | } |
| 357 | + $this->activeConns = array(); |
353 | 358 | } |
354 | 359 | |
355 | 360 | /** |
— | — | @@ -356,29 +361,17 @@ |
357 | 362 | * This should avoid throwing any exceptions. |
358 | 363 | * |
359 | 364 | * @param $key string |
360 | | - * @return int |
| 365 | + * @return integer |
361 | 366 | */ |
362 | 367 | protected function getBucketFromKey( $key ) { |
363 | 368 | $hash = str_pad( md5( $key ), 32, '0', STR_PAD_LEFT ); // 32 char hash |
364 | 369 | $prefix = substr( $hash, 0, 2 ); // first 2 hex chars (8 bits) |
365 | | - return ( intval( base_convert( $prefix, 16, 10 ) ) % count( $this->serverMap ) ); |
366 | | - } |
367 | | - |
368 | | - /** |
369 | | - * Get the server name for lock key. |
370 | | - * This should avoid throwing any exceptions. |
371 | | - * |
372 | | - * @param $key string |
373 | | - * @return string|null |
374 | | - */ |
375 | | - protected function getDBServerFromKey( $key ) { |
376 | | - $bucket = $this->getBucketFromKey( $key ); |
377 | | - if ( isset( $this->serverMap[$bucket] ) ) { |
378 | | - return $this->serverMap[$bucket]; |
379 | | - } else { |
380 | | - wfWarn( "No key for bucket $bucket in serverMap." ); |
381 | | - return null; // disabled? bad config? |
| 370 | + $bucket = intval( base_convert( $prefix, 16, 10 ) ) % count( $this->serverMap ); |
| 371 | + // Sanity check that at least one server is handling this bucket |
| 372 | + if ( !isset( $this->serverMap[$bucket] ) ) { |
| 373 | + return null; // bad config? |
382 | 374 | } |
| 375 | + return $bucket; |
383 | 376 | } |
384 | 377 | } |
385 | 378 | |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php |
— | — | @@ -258,7 +258,7 @@ |
259 | 259 | // Get params for this operation |
260 | 260 | $params = $operation; |
261 | 261 | unset( $params['operation'] ); // don't need this |
262 | | - // Add operation on to the list of things to do |
| 262 | + // Append the FileOp class |
263 | 263 | $performOps[] = new $class( $params ); |
264 | 264 | } else { |
265 | 265 | throw new MWException( "Operation `$opName` is not supported." ); |
— | — | @@ -270,31 +270,49 @@ |
271 | 271 | |
272 | 272 | final public function doOperations( array $ops ) { |
273 | 273 | $status = Status::newGood(); |
| 274 | + |
274 | 275 | // Build up a list of FileOps... |
275 | 276 | $performOps = $this->getOperations( $ops ); |
| 277 | + |
| 278 | + // Build up a list of files to lock... |
| 279 | + $filesToLock = array(); |
| 280 | + foreach ( $performOps as $index => $fileOp ) { |
| 281 | + $filesToLock = array_merge( $filesToLock, $fileOp->storagePathsToLock() ); |
| 282 | + } |
| 283 | + $filesToLock = array_unique( $filesToLock ); // avoid warnings |
| 284 | + |
| 285 | + // Try to lock those files... |
| 286 | + $status->merge( $this->lockFiles( $filesToLock ) ); |
| 287 | + if ( !$status->isOK() ) { |
| 288 | + return $status; // abort |
| 289 | + } |
| 290 | + |
276 | 291 | // Attempt each operation; abort on failure... |
277 | | - foreach ( $performOps as $index => $transaction ) { |
278 | | - $subStatus = $transaction->attempt(); |
279 | | - $status->merge( $subStatus ); |
280 | | - if ( !$subStatus->isOK() ) { // operation failed? |
| 292 | + foreach ( $performOps as $index => $fileOp ) { |
| 293 | + $status->merge( $fileOp->attempt() ); |
| 294 | + if ( !$status->isOK() ) { // operation failed? |
281 | 295 | // Revert everything done so far and abort. |
282 | 296 | // Do this by reverting each previous operation in reverse order. |
283 | 297 | $pos = $index - 1; // last one failed; no need to revert() |
284 | 298 | while ( $pos >= 0 ) { |
285 | | - $subStatus = $performOps[$pos]->revert(); |
286 | | - $status->merge( $subStatus ); |
| 299 | + $status->merge( $performOps[$pos]->revert() ); |
287 | 300 | $pos--; |
288 | 301 | } |
289 | 302 | return $status; |
290 | 303 | } |
291 | 304 | } |
| 305 | + |
292 | 306 | // Finish each operation... |
293 | | - foreach ( $performOps as $index => $transaction ) { |
294 | | - $subStatus = $transaction->finish(); |
295 | | - $status->merge( $subStatus ); |
| 307 | + foreach ( $performOps as $index => $fileOp ) { |
| 308 | + $status->merge( $fileOp->finish() ); |
296 | 309 | } |
297 | | - // Make sure status is OK, despite any finish() fatals |
| 310 | + |
| 311 | + // Unlock all the files |
| 312 | + $status->merge( $this->unlockFiles( $filesToLock ) ); |
| 313 | + |
| 314 | + // Make sure status is OK, despite any finish() or unlockFiles() fatals |
298 | 315 | $status->setResult( true ); |
| 316 | + |
299 | 317 | return $status; |
300 | 318 | } |
301 | 319 | |
— | — | @@ -354,10 +372,8 @@ |
355 | 373 | throw new MWException( "Cannot attempt operation called twice." ); |
356 | 374 | } |
357 | 375 | $this->state = self::STATE_ATTEMPTED; |
358 | | - $status = $this->setLocks(); |
359 | | - if ( $status->isOK() ) { |
360 | | - $status = $this->doAttempt(); |
361 | | - } else { |
| 376 | + $status = $this->doAttempt(); |
| 377 | + if ( !$status->isOK() ) { |
362 | 378 | $this->failedAttempt = true; |
363 | 379 | } |
364 | 380 | return $status; |
— | — | @@ -373,12 +389,11 @@ |
374 | 390 | throw new MWException( "Cannot rollback an unstarted or finished operation." ); |
375 | 391 | } |
376 | 392 | $this->state = self::STATE_DONE; |
377 | | - if ( !$this->failedAttempt ) { |
| 393 | + if ( $this->failedAttempt ) { |
| 394 | + $status = Status::newGood(); // nothing to revert |
| 395 | + } else { |
378 | 396 | $status = $this->doRevert(); |
379 | | - } else { |
380 | | - $status = Status::newGood(); // nothing to revert |
381 | 397 | } |
382 | | - $this->unsetLocks(); |
383 | 398 | return $status; |
384 | 399 | } |
385 | 400 | |
— | — | @@ -392,39 +407,20 @@ |
393 | 408 | throw new MWException( "Cannot cleanup an unstarted or finished operation." ); |
394 | 409 | } |
395 | 410 | $this->state = self::STATE_DONE; |
396 | | - if ( !$this->failedAttempt ) { |
| 411 | + if ( $this->failedAttempt ) { |
| 412 | + $status = Status::newGood(); // nothing to revert |
| 413 | + } else { |
397 | 414 | $status = $this->doFinish(); |
398 | | - } else { |
399 | | - $status = Status::newGood(); // nothing to revert |
400 | 415 | } |
401 | | - $this->unsetLocks(); |
402 | 416 | return $status; |
403 | 417 | } |
404 | 418 | |
405 | 419 | /** |
406 | | - * Try to lock any files before changing them |
407 | | - * |
408 | | - * @return Status |
409 | | - */ |
410 | | - private function setLocks() { |
411 | | - return $this->backend->lockFiles( $this->storagePathsToLock() ); |
412 | | - } |
413 | | - |
414 | | - /** |
415 | | - * Try to unlock any files that this locked |
416 | | - * |
417 | | - * @return Status |
418 | | - */ |
419 | | - private function unsetLocks() { |
420 | | - return $this->backend->unlockFiles( $this->storagePathsToLock() ); |
421 | | - } |
422 | | - |
423 | | - /** |
424 | 420 | * Get a list of storage paths to lock for this operation |
425 | 421 | * |
426 | 422 | * @return Array |
427 | 423 | */ |
428 | | - protected function storagePathsToLock() { |
| 424 | + public function storagePathsToLock() { |
429 | 425 | return array(); |
430 | 426 | } |
431 | 427 | |
— | — | @@ -632,6 +628,10 @@ |
633 | 629 | } |
634 | 630 | return $status; |
635 | 631 | } |
| 632 | + |
| 633 | + function storagePathsToLock() { |
| 634 | + return array( $this->params['source'], $this->params['dest'] ); |
| 635 | + } |
636 | 636 | } |
637 | 637 | |
638 | 638 | /** |
Index: branches/FileBackend/phase3/includes/filerepo/ForeignDBViaLBRepo.php |
— | — | @@ -30,6 +30,7 @@ |
31 | 31 | function getSlaveDB() { |
32 | 32 | return wfGetDB( DB_SLAVE, array(), $this->wiki ); |
33 | 33 | } |
| 34 | + |
34 | 35 | function hasSharedCache() { |
35 | 36 | return $this->hasSharedCache; |
36 | 37 | } |