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