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