MediaWiki  master
FileBackendMultiWrite.php
Go to the documentation of this file.
1 <?php
25 
47  protected $backends = [];
48 
50  protected $masterIndex = -1;
52  protected $readIndex = -1;
53 
55  protected $syncChecks = 0;
57  protected $autoResync = false;
58 
60  protected $asyncWrites = false;
61 
63  const CHECK_SIZE = 1;
65  const CHECK_TIME = 2;
67  const CHECK_SHA1 = 4;
68 
97  public function __construct( array $config ) {
98  parent::__construct( $config );
99  $this->syncChecks = $config['syncChecks'] ?? self::CHECK_SIZE;
100  $this->autoResync = $config['autoResync'] ?? false;
101  $this->asyncWrites = isset( $config['replication'] ) && $config['replication'] === 'async';
102  // Construct backends here rather than via registration
103  // to keep these backends hidden from outside the proxy.
104  $namesUsed = [];
105  foreach ( $config['backends'] as $index => $config ) {
106  $name = $config['name'];
107  if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
108  throw new LogicException( "Two or more backends defined with the name $name." );
109  }
110  $namesUsed[$name] = 1;
111  // Alter certain sub-backend settings for sanity
112  unset( $config['readOnly'] ); // use proxy backend setting
113  unset( $config['fileJournal'] ); // use proxy backend journal
114  unset( $config['lockManager'] ); // lock under proxy backend
115  $config['domainId'] = $this->domainId; // use the proxy backend wiki ID
116  if ( !empty( $config['isMultiMaster'] ) ) {
117  if ( $this->masterIndex >= 0 ) {
118  throw new LogicException( 'More than one master backend defined.' );
119  }
120  $this->masterIndex = $index; // this is the "master"
121  $config['fileJournal'] = $this->fileJournal; // log under proxy backend
122  }
123  if ( !empty( $config['readAffinity'] ) ) {
124  $this->readIndex = $index; // prefer this for reads
125  }
126  // Create sub-backend object
127  if ( !isset( $config['class'] ) ) {
128  throw new InvalidArgumentException( 'No class given for a backend config.' );
129  }
130  $class = $config['class'];
131  $this->backends[$index] = new $class( $config );
132  }
133  if ( $this->masterIndex < 0 ) { // need backends and must have a master
134  throw new LogicException( 'No master backend defined.' );
135  }
136  if ( $this->readIndex < 0 ) {
137  $this->readIndex = $this->masterIndex; // default
138  }
139  }
140 
141  final protected function doOperationsInternal( array $ops, array $opts ) {
142  $status = $this->newStatus();
143 
144  $mbe = $this->backends[$this->masterIndex]; // convenience
145 
146  // Acquire any locks as needed
147  if ( empty( $opts['nonLocking'] ) ) {
149  $scopeLock = $this->getScopedLocksForOps( $ops, $status );
150  if ( !$status->isOK() ) {
151  return $status; // abort
152  }
153  }
154  // Clear any cache entries (after locks acquired)
155  $this->clearCache();
156  $opts['preserveCache'] = true; // only locked files are cached
157  // Get the list of paths to read/write
158  $relevantPaths = $this->fileStoragePathsForOps( $ops );
159  // Check if the paths are valid and accessible on all backends
160  $status->merge( $this->accessibilityCheck( $relevantPaths ) );
161  if ( !$status->isOK() ) {
162  return $status; // abort
163  }
164  // Do a consistency check to see if the backends are consistent
165  $syncStatus = $this->consistencyCheck( $relevantPaths );
166  if ( !$syncStatus->isOK() ) {
167  $this->logger->error(
168  __METHOD__ . ": failed sync check: " . FormatJson::encode( $relevantPaths )
169  );
170  // Try to resync the clone backends to the master on the spot
171  if (
172  $this->autoResync === false ||
173  !$this->resyncFiles( $relevantPaths, $this->autoResync )->isOK()
174  ) {
175  $status->merge( $syncStatus );
176 
177  return $status; // abort
178  }
179  }
180  // Actually attempt the operation batch on the master backend
181  $realOps = $this->substOpBatchPaths( $ops, $mbe );
182  $masterStatus = $mbe->doOperations( $realOps, $opts );
183  $status->merge( $masterStatus );
184  // Propagate the operations to the clone backends if there were no unexpected errors
185  // and everything didn't fail due to predicted errors. If $ops only had one operation,
186  // this might avoid backend sync inconsistencies.
187  if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
188  foreach ( $this->backends as $index => $backend ) {
189  if ( $index === $this->masterIndex ) {
190  continue; // done already
191  }
192 
193  $realOps = $this->substOpBatchPaths( $ops, $backend );
194  if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
195  // Bind $scopeLock to the callback to preserve locks
197  function () use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
198  $this->logger->error(
199  "'{$backend->getName()}' async replication; paths: " .
200  FormatJson::encode( $relevantPaths )
201  );
202  $backend->doOperations( $realOps, $opts );
203  }
204  );
205  } else {
206  $this->logger->error(
207  "'{$backend->getName()}' sync replication; paths: " .
208  FormatJson::encode( $relevantPaths )
209  );
210  $status->merge( $backend->doOperations( $realOps, $opts ) );
211  }
212  }
213  }
214  // Make 'success', 'successCount', and 'failCount' fields reflect
215  // the overall operation, rather than all the batches for each backend.
216  // Do this by only using success values from the master backend's batch.
217  $status->success = $masterStatus->success;
218  $status->successCount = $masterStatus->successCount;
219  $status->failCount = $masterStatus->failCount;
220 
221  return $status;
222  }
223 
233  public function consistencyCheck( array $paths ) {
234  $status = $this->newStatus();
235  if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
236  return $status; // skip checks
237  }
238 
239  // Preload all of the stat info in as few round trips as possible
240  foreach ( $this->backends as $backend ) {
241  $realPaths = $this->substPaths( $paths, $backend );
242  $backend->preloadFileStat( [ 'srcs' => $realPaths, 'latest' => true ] );
243  }
244 
245  foreach ( $paths as $path ) {
246  $params = [ 'src' => $path, 'latest' => true ];
247  // Get the state of the file on the master backend
248  $masterBackend = $this->backends[$this->masterIndex];
249  $masterParams = $this->substOpPaths( $params, $masterBackend );
250  $masterStat = $masterBackend->getFileStat( $masterParams );
251  if ( $masterStat === self::STAT_ERROR ) {
252  $status->fatal( 'backend-fail-stat', $path );
253  continue;
254  }
255  if ( $this->syncChecks & self::CHECK_SHA1 ) {
256  $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
257  if ( ( $masterSha1 !== false ) !== (bool)$masterStat ) {
258  $status->fatal( 'backend-fail-hash', $path );
259  continue;
260  }
261  } else {
262  $masterSha1 = null; // unused
263  }
264 
265  // Check if all clone backends agree with the master...
266  foreach ( $this->backends as $index => $cloneBackend ) {
267  if ( $index === $this->masterIndex ) {
268  continue; // master
269  }
270 
271  // Get the state of the file on the clone backend
272  $cloneParams = $this->substOpPaths( $params, $cloneBackend );
273  $cloneStat = $cloneBackend->getFileStat( $cloneParams );
274 
275  if ( $masterStat ) {
276  // File exists in the master backend
277  if ( !$cloneStat ) {
278  // File is missing from the clone backend
279  $status->fatal( 'backend-fail-synced', $path );
280  } elseif (
281  ( $this->syncChecks & self::CHECK_SIZE ) &&
282  $cloneStat['size'] !== $masterStat['size']
283  ) {
284  // File in the clone backend is different
285  $status->fatal( 'backend-fail-synced', $path );
286  } elseif (
287  ( $this->syncChecks & self::CHECK_TIME ) &&
288  abs(
289  ConvertibleTimestamp::convert( TS_UNIX, $masterStat['mtime'] ) -
290  ConvertibleTimestamp::convert( TS_UNIX, $cloneStat['mtime'] )
291  ) > 30
292  ) {
293  // File in the clone backend is significantly newer or older
294  $status->fatal( 'backend-fail-synced', $path );
295  } elseif (
296  ( $this->syncChecks & self::CHECK_SHA1 ) &&
297  $cloneBackend->getFileSha1Base36( $cloneParams ) !== $masterSha1
298  ) {
299  // File in the clone backend is different
300  $status->fatal( 'backend-fail-synced', $path );
301  }
302  } else {
303  // File does not exist in the master backend
304  if ( $cloneStat ) {
305  // Stray file exists in the clone backend
306  $status->fatal( 'backend-fail-synced', $path );
307  }
308  }
309  }
310  }
311 
312  return $status;
313  }
314 
321  public function accessibilityCheck( array $paths ) {
322  $status = $this->newStatus();
323  if ( count( $this->backends ) <= 1 ) {
324  return $status; // skip checks
325  }
326 
327  foreach ( $paths as $path ) {
328  foreach ( $this->backends as $backend ) {
329  $realPath = $this->substPaths( $path, $backend );
330  if ( !$backend->isPathUsableInternal( $realPath ) ) {
331  $status->fatal( 'backend-fail-usable', $path );
332  }
333  }
334  }
335 
336  return $status;
337  }
338 
349  public function resyncFiles( array $paths, $resyncMode = true ) {
350  $status = $this->newStatus();
351 
352  $fname = __METHOD__;
353  foreach ( $paths as $path ) {
354  $params = [ 'src' => $path, 'latest' => true ];
355  // Get the state of the file on the master backend
356  $masterBackend = $this->backends[$this->masterIndex];
357  $masterParams = $this->substOpPaths( $params, $masterBackend );
358  $masterPath = $masterParams['src'];
359  $masterStat = $masterBackend->getFileStat( $masterParams );
360  if ( $masterStat === self::STAT_ERROR ) {
361  $status->fatal( 'backend-fail-stat', $path );
362  $this->logger->error( "$fname: file '$masterPath' is not available" );
363  continue;
364  }
365  $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
366  if ( ( $masterSha1 !== false ) !== (bool)$masterStat ) {
367  $status->fatal( 'backend-fail-hash', $path );
368  $this->logger->error( "$fname: file '$masterPath' hash does not match stat" );
369  continue;
370  }
371 
372  // Check of all clone backends agree with the master...
373  foreach ( $this->backends as $index => $cloneBackend ) {
374  if ( $index === $this->masterIndex ) {
375  continue; // master
376  }
377 
378  // Get the state of the file on the clone backend
379  $cloneParams = $this->substOpPaths( $params, $cloneBackend );
380  $clonePath = $cloneParams['src'];
381  $cloneStat = $cloneBackend->getFileStat( $cloneParams );
382  if ( $cloneStat === self::STAT_ERROR ) {
383  $status->fatal( 'backend-fail-stat', $path );
384  $this->logger->error( "$fname: file '$clonePath' is not available" );
385  continue;
386  }
387  $cloneSha1 = $cloneBackend->getFileSha1Base36( $cloneParams );
388  if ( ( $cloneSha1 !== false ) !== (bool)$cloneStat ) {
389  $status->fatal( 'backend-fail-hash', $path );
390  $this->logger->error( "$fname: file '$clonePath' hash does not match stat" );
391  continue;
392  }
393 
394  if ( $masterSha1 === $cloneSha1 ) {
395  // File is either the same in both backends or absent from both backends
396  $this->logger->debug( "$fname: file '$clonePath' matches '$masterPath'" );
397  } elseif ( $masterSha1 !== false ) {
398  // File is either missing from or different in the clone backend
399  if (
400  $resyncMode === 'conservative' &&
401  $cloneStat &&
402  $cloneStat['mtime'] > $masterStat['mtime']
403  ) {
404  // Do not replace files with older ones; reduces the risk of data loss
405  $status->fatal( 'backend-fail-synced', $path );
406  } else {
407  // Copy the master backend file to the clone backend in overwrite mode
408  $fsFile = $masterBackend->getLocalReference( $masterParams );
409  $status->merge( $cloneBackend->quickStore( [
410  'src' => $fsFile,
411  'dst' => $clonePath
412  ] ) );
413  }
414  } elseif ( $masterStat === false ) {
415  // Stray file exists in the clone backend
416  if ( $resyncMode === 'conservative' ) {
417  // Do not delete stray files; reduces the risk of data loss
418  $status->fatal( 'backend-fail-synced', $path );
419  } else {
420  // Delete the stay file from the clone backend
421  $status->merge( $cloneBackend->quickDelete( [ 'src' => $clonePath ] ) );
422  }
423  }
424  }
425  }
426 
427  if ( !$status->isOK() ) {
428  $this->logger->error( "$fname: failed to resync: " . FormatJson::encode( $paths ) );
429  }
430 
431  return $status;
432  }
433 
440  protected function fileStoragePathsForOps( array $ops ) {
441  $paths = [];
442  foreach ( $ops as $op ) {
443  if ( isset( $op['src'] ) ) {
444  // For things like copy/move/delete with "ignoreMissingSource" and there
445  // is no source file, nothing should happen and there should be no errors.
446  if ( empty( $op['ignoreMissingSource'] )
447  || $this->fileExists( [ 'src' => $op['src'] ] )
448  ) {
449  $paths[] = $op['src'];
450  }
451  }
452  if ( isset( $op['srcs'] ) ) {
453  $paths = array_merge( $paths, $op['srcs'] );
454  }
455  if ( isset( $op['dst'] ) ) {
456  $paths[] = $op['dst'];
457  }
458  }
459 
460  return array_values( array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) ) );
461  }
462 
471  protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
472  $newOps = []; // operations
473  foreach ( $ops as $op ) {
474  $newOp = $op; // operation
475  foreach ( [ 'src', 'srcs', 'dst', 'dir' ] as $par ) {
476  if ( isset( $newOp[$par] ) ) { // string or array
477  $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
478  }
479  }
480  $newOps[] = $newOp;
481  }
482 
483  return $newOps;
484  }
485 
493  protected function substOpPaths( array $ops, FileBackendStore $backend ) {
494  $newOps = $this->substOpBatchPaths( [ $ops ], $backend );
495 
496  return $newOps[0];
497  }
498 
506  protected function substPaths( $paths, FileBackendStore $backend ) {
507  return preg_replace(
508  '!^mwstore://' . preg_quote( $this->name, '!' ) . '/!',
509  StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
510  $paths // string or array
511  );
512  }
513 
521  protected function unsubstPaths( $paths, FileBackendStore $backend ) {
522  return preg_replace(
523  '!^mwstore://' . preg_quote( $backend->getName(), '!' ) . '/!',
524  StringUtils::escapeRegexReplacement( "mwstore://{$this->name}/" ),
525  $paths // string or array
526  );
527  }
528 
533  protected function hasVolatileSources( array $ops ) {
534  foreach ( $ops as $op ) {
535  if ( $op['op'] === 'store' && !isset( $op['srcRef'] ) ) {
536  return true; // source file might be deleted anytime after do*Operations()
537  }
538  }
539 
540  return false;
541  }
542 
543  protected function doQuickOperationsInternal( array $ops ) {
544  $status = $this->newStatus();
545  // Do the operations on the master backend; setting StatusValue fields
546  $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
547  $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
548  $status->merge( $masterStatus );
549  // Propagate the operations to the clone backends...
550  foreach ( $this->backends as $index => $backend ) {
551  if ( $index === $this->masterIndex ) {
552  continue; // done already
553  }
554 
555  $realOps = $this->substOpBatchPaths( $ops, $backend );
556  if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
558  function () use ( $backend, $realOps ) {
559  $backend->doQuickOperations( $realOps );
560  }
561  );
562  } else {
563  $status->merge( $backend->doQuickOperations( $realOps ) );
564  }
565  }
566  // Make 'success', 'successCount', and 'failCount' fields reflect
567  // the overall operation, rather than all the batches for each backend.
568  // Do this by only using success values from the master backend's batch.
569  $status->success = $masterStatus->success;
570  $status->successCount = $masterStatus->successCount;
571  $status->failCount = $masterStatus->failCount;
572 
573  return $status;
574  }
575 
576  protected function doPrepare( array $params ) {
577  return $this->doDirectoryOp( 'prepare', $params );
578  }
579 
580  protected function doSecure( array $params ) {
581  return $this->doDirectoryOp( 'secure', $params );
582  }
583 
584  protected function doPublish( array $params ) {
585  return $this->doDirectoryOp( 'publish', $params );
586  }
587 
588  protected function doClean( array $params ) {
589  return $this->doDirectoryOp( 'clean', $params );
590  }
591 
597  protected function doDirectoryOp( $method, array $params ) {
598  $status = $this->newStatus();
599 
600  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
601  $masterStatus = $this->backends[$this->masterIndex]->$method( $realParams );
602  $status->merge( $masterStatus );
603 
604  foreach ( $this->backends as $index => $backend ) {
605  if ( $index === $this->masterIndex ) {
606  continue; // already done
607  }
608 
609  $realParams = $this->substOpPaths( $params, $backend );
610  if ( $this->asyncWrites ) {
612  function () use ( $backend, $method, $realParams ) {
613  $backend->$method( $realParams );
614  }
615  );
616  } else {
617  $status->merge( $backend->$method( $realParams ) );
618  }
619  }
620 
621  return $status;
622  }
623 
624  public function concatenate( array $params ) {
625  $status = $this->newStatus();
626  // We are writing to an FS file, so we don't need to do this per-backend
627  $index = $this->getReadIndexFromParams( $params );
628  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
629 
630  $status->merge( $this->backends[$index]->concatenate( $realParams ) );
631 
632  return $status;
633  }
634 
635  public function fileExists( array $params ) {
636  $index = $this->getReadIndexFromParams( $params );
637  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
638 
639  return $this->backends[$index]->fileExists( $realParams );
640  }
641 
642  public function getFileTimestamp( array $params ) {
643  $index = $this->getReadIndexFromParams( $params );
644  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
645 
646  return $this->backends[$index]->getFileTimestamp( $realParams );
647  }
648 
649  public function getFileSize( array $params ) {
650  $index = $this->getReadIndexFromParams( $params );
651  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
652 
653  return $this->backends[$index]->getFileSize( $realParams );
654  }
655 
656  public function getFileStat( array $params ) {
657  $index = $this->getReadIndexFromParams( $params );
658  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
659 
660  return $this->backends[$index]->getFileStat( $realParams );
661  }
662 
663  public function getFileXAttributes( array $params ) {
664  $index = $this->getReadIndexFromParams( $params );
665  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
666 
667  return $this->backends[$index]->getFileXAttributes( $realParams );
668  }
669 
670  public function getFileContentsMulti( array $params ) {
671  $index = $this->getReadIndexFromParams( $params );
672  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
673 
674  $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
675 
676  $contents = []; // (path => FSFile) mapping using the proxy backend's name
677  foreach ( $contentsM as $path => $data ) {
678  $contents[$this->unsubstPaths( $path, $this->backends[$index] )] = $data;
679  }
680 
681  return $contents;
682  }
683 
684  public function getFileSha1Base36( array $params ) {
685  $index = $this->getReadIndexFromParams( $params );
686  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
687 
688  return $this->backends[$index]->getFileSha1Base36( $realParams );
689  }
690 
691  public function getFileProps( array $params ) {
692  $index = $this->getReadIndexFromParams( $params );
693  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
694 
695  return $this->backends[$index]->getFileProps( $realParams );
696  }
697 
698  public function streamFile( array $params ) {
699  $index = $this->getReadIndexFromParams( $params );
700  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
701 
702  return $this->backends[$index]->streamFile( $realParams );
703  }
704 
705  public function getLocalReferenceMulti( array $params ) {
706  $index = $this->getReadIndexFromParams( $params );
707  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
708 
709  $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
710 
711  $fsFiles = []; // (path => FSFile) mapping using the proxy backend's name
712  foreach ( $fsFilesM as $path => $fsFile ) {
713  $fsFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $fsFile;
714  }
715 
716  return $fsFiles;
717  }
718 
719  public function getLocalCopyMulti( array $params ) {
720  $index = $this->getReadIndexFromParams( $params );
721  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
722 
723  $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
724 
725  $tempFiles = []; // (path => TempFSFile) mapping using the proxy backend's name
726  foreach ( $tempFilesM as $path => $tempFile ) {
727  $tempFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $tempFile;
728  }
729 
730  return $tempFiles;
731  }
732 
733  public function getFileHttpUrl( array $params ) {
734  $index = $this->getReadIndexFromParams( $params );
735  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
736 
737  return $this->backends[$index]->getFileHttpUrl( $realParams );
738  }
739 
740  public function directoryExists( array $params ) {
741  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
742 
743  return $this->backends[$this->masterIndex]->directoryExists( $realParams );
744  }
745 
746  public function getDirectoryList( array $params ) {
747  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
748 
749  return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
750  }
751 
752  public function getFileList( array $params ) {
753  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
754 
755  return $this->backends[$this->masterIndex]->getFileList( $realParams );
756  }
757 
758  public function getFeatures() {
759  return $this->backends[$this->masterIndex]->getFeatures();
760  }
761 
762  public function clearCache( array $paths = null ) {
763  foreach ( $this->backends as $backend ) {
764  $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
765  $backend->clearCache( $realPaths );
766  }
767  }
768 
769  public function preloadCache( array $paths ) {
770  $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] );
771  $this->backends[$this->readIndex]->preloadCache( $realPaths );
772  }
773 
774  public function preloadFileStat( array $params ) {
775  $index = $this->getReadIndexFromParams( $params );
776  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
777 
778  return $this->backends[$index]->preloadFileStat( $realParams );
779  }
780 
781  public function getScopedLocksForOps( array $ops, StatusValue $status ) {
782  $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
783  $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
784  // Get the paths to lock from the master backend
785  $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
786  // Get the paths under the proxy backend's name
787  $pbPaths = [
789  $paths[LockManager::LOCK_UW],
790  $this->backends[$this->masterIndex]
791  ),
793  $paths[LockManager::LOCK_EX],
794  $this->backends[$this->masterIndex]
795  )
796  ];
797 
798  // Actually acquire the locks
799  return $this->getScopedFileLocks( $pbPaths, 'mixed', $status );
800  }
801 
806  protected function getReadIndexFromParams( array $params ) {
807  return !empty( $params['latest'] ) ? $this->masterIndex : $this->readIndex;
808  }
809 }
FileJournal $fileJournal
FileBackendStore [] $backends
Prioritized list of FileBackendStore objects.
substPaths( $paths, FileBackendStore $backend)
Substitute the backend of storage paths with an internal backend&#39;s name.
substOpPaths(array $ops, FileBackendStore $backend)
Same as substOpBatchPaths() but for a single operation.
substOpBatchPaths(array $ops, FileBackendStore $backend)
Substitute the backend name in storage path parameters for a set of operations with that of a given i...
string $domainId
Unique domain name.
Definition: FileBackend.php:99
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter...
doDirectoryOp( $method, array $params)
accessibilityCheck(array $paths)
Check that a set of file paths are usable across all internal backends.
clearCache(array $paths=null)
getName()
Get the unique backend name.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:115
fileStoragePathsForOps(array $ops)
Get a list of file storage paths to read or write for a list of operations.
consistencyCheck(array $paths)
Check that a set of files are consistent across all internal backends.
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
const LOCK_UW
Definition: LockManager.php:69
const LOCK_EX
Definition: LockManager.php:70
int $readIndex
Index of read affinity backend.
getLocalReferenceMulti(array $params)
newStatus(... $args)
Yields the result of the status wrapper callback on either:
doOperationsInternal(array $ops, array $opts)
getReadIndexFromParams(array $params)
string $name
Unique backend name.
Definition: FileBackend.php:96
Base class for all backends using particular storage medium.
resyncFiles(array $paths, $resyncMode=true)
Check that a set of files are consistent across all internal backends and re-synchronize those files ...
Proxy backend that mirrors writes to several internal backends.
Base class for all file backend classes (including multi-write backends).
Definition: FileBackend.php:94
unsubstPaths( $paths, FileBackendStore $backend)
Substitute the backend of internal storage paths with the proxy backend&#39;s name.
__construct(array $config)
Construct a proxy backend that consists of several internal backends.
int $masterIndex
Index of master backend.
return true
Definition: router.php:92
getScopedLocksForOps(array $ops, StatusValue $status)