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 => $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  $scopeLock = null;
148  if ( empty( $opts['nonLocking'] ) ) {
149  $scopeLock = $this->getScopedLocksForOps( $ops, $status );
150  if ( !$status->isOK() ) {
151  return $status; // abort
152  }
153  }
154  // Get the list of paths to read/write
155  $relevantPaths = $this->fileStoragePathsForOps( $ops );
156  // Clear any cache entries (after locks acquired)
157  $this->clearCache( $relevantPaths );
158  $opts['preserveCache'] = true; // only locked files are cached
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  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
403  $cloneStat['mtime'] > $masterStat['mtime']
404  ) {
405  // Do not replace files with older ones; reduces the risk of data loss
406  $status->fatal( 'backend-fail-synced', $path );
407  } else {
408  // Copy the master backend file to the clone backend in overwrite mode
409  $fsFile = $masterBackend->getLocalReference( $masterParams );
410  $status->merge( $cloneBackend->quickStore( [
411  'src' => $fsFile,
412  'dst' => $clonePath
413  ] ) );
414  }
415  } elseif ( $masterStat === false ) {
416  // Stray file exists in the clone backend
417  if ( $resyncMode === 'conservative' ) {
418  // Do not delete stray files; reduces the risk of data loss
419  $status->fatal( 'backend-fail-synced', $path );
420  } else {
421  // Delete the stay file from the clone backend
422  $status->merge( $cloneBackend->quickDelete( [ 'src' => $clonePath ] ) );
423  }
424  }
425  }
426  }
427 
428  if ( !$status->isOK() ) {
429  $this->logger->error( "$fname: failed to resync: " . FormatJson::encode( $paths ) );
430  }
431 
432  return $status;
433  }
434 
441  protected function fileStoragePathsForOps( array $ops ) {
442  $paths = [];
443  foreach ( $ops as $op ) {
444  if ( isset( $op['src'] ) ) {
445  // For things like copy/move/delete with "ignoreMissingSource" and there
446  // is no source file, nothing should happen and there should be no errors.
447  if ( empty( $op['ignoreMissingSource'] )
448  || $this->fileExists( [ 'src' => $op['src'] ] )
449  ) {
450  $paths[] = $op['src'];
451  }
452  }
453  if ( isset( $op['srcs'] ) ) {
454  $paths = array_merge( $paths, $op['srcs'] );
455  }
456  if ( isset( $op['dst'] ) ) {
457  $paths[] = $op['dst'];
458  }
459  }
460 
461  return array_values( array_unique( array_filter( $paths, 'FileBackend::isStoragePath' ) ) );
462  }
463 
472  protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
473  $newOps = []; // operations
474  foreach ( $ops as $op ) {
475  $newOp = $op; // operation
476  foreach ( [ 'src', 'srcs', 'dst', 'dir' ] as $par ) {
477  if ( isset( $newOp[$par] ) ) { // string or array
478  $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
479  }
480  }
481  $newOps[] = $newOp;
482  }
483 
484  return $newOps;
485  }
486 
494  protected function substOpPaths( array $ops, FileBackendStore $backend ) {
495  $newOps = $this->substOpBatchPaths( [ $ops ], $backend );
496 
497  return $newOps[0];
498  }
499 
507  protected function substPaths( $paths, FileBackendStore $backend ) {
508  return preg_replace(
509  '!^mwstore://' . preg_quote( $this->name, '!' ) . '/!',
510  StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
511  $paths // string or array
512  );
513  }
514 
522  protected function unsubstPaths( $paths, FileBackendStore $backend ) {
523  return preg_replace(
524  '!^mwstore://' . preg_quote( $backend->getName(), '!' ) . '/!',
525  StringUtils::escapeRegexReplacement( "mwstore://{$this->name}/" ),
526  $paths // string or array
527  );
528  }
529 
534  protected function hasVolatileSources( array $ops ) {
535  foreach ( $ops as $op ) {
536  if ( $op['op'] === 'store' && !isset( $op['srcRef'] ) ) {
537  return true; // source file might be deleted anytime after do*Operations()
538  }
539  }
540 
541  return false;
542  }
543 
544  protected function doQuickOperationsInternal( array $ops, array $opts ) {
545  $status = $this->newStatus();
546  // Do the operations on the master backend; setting StatusValue fields
547  $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
548  $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
549  $status->merge( $masterStatus );
550  // Propagate the operations to the clone backends...
551  foreach ( $this->backends as $index => $backend ) {
552  if ( $index === $this->masterIndex ) {
553  continue; // done already
554  }
555 
556  $realOps = $this->substOpBatchPaths( $ops, $backend );
557  if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
559  function () use ( $backend, $realOps ) {
560  $backend->doQuickOperations( $realOps );
561  }
562  );
563  } else {
564  $status->merge( $backend->doQuickOperations( $realOps ) );
565  }
566  }
567  // Make 'success', 'successCount', and 'failCount' fields reflect
568  // the overall operation, rather than all the batches for each backend.
569  // Do this by only using success values from the master backend's batch.
570  $status->success = $masterStatus->success;
571  $status->successCount = $masterStatus->successCount;
572  $status->failCount = $masterStatus->failCount;
573 
574  return $status;
575  }
576 
577  protected function doPrepare( array $params ) {
578  return $this->doDirectoryOp( 'prepare', $params );
579  }
580 
581  protected function doSecure( array $params ) {
582  return $this->doDirectoryOp( 'secure', $params );
583  }
584 
585  protected function doPublish( array $params ) {
586  return $this->doDirectoryOp( 'publish', $params );
587  }
588 
589  protected function doClean( array $params ) {
590  return $this->doDirectoryOp( 'clean', $params );
591  }
592 
598  protected function doDirectoryOp( $method, array $params ) {
599  $status = $this->newStatus();
600 
601  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
602  $masterStatus = $this->backends[$this->masterIndex]->$method( $realParams );
603  $status->merge( $masterStatus );
604 
605  foreach ( $this->backends as $index => $backend ) {
606  if ( $index === $this->masterIndex ) {
607  continue; // already done
608  }
609 
610  $realParams = $this->substOpPaths( $params, $backend );
611  if ( $this->asyncWrites ) {
613  function () use ( $backend, $method, $realParams ) {
614  $backend->$method( $realParams );
615  }
616  );
617  } else {
618  $status->merge( $backend->$method( $realParams ) );
619  }
620  }
621 
622  return $status;
623  }
624 
625  public function concatenate( array $params ) {
626  $status = $this->newStatus();
627  // We are writing to an FS file, so we don't need to do this per-backend
628  $index = $this->getReadIndexFromParams( $params );
629  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
630 
631  $status->merge( $this->backends[$index]->concatenate( $realParams ) );
632 
633  return $status;
634  }
635 
636  public function fileExists( array $params ) {
637  $index = $this->getReadIndexFromParams( $params );
638  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
639 
640  return $this->backends[$index]->fileExists( $realParams );
641  }
642 
643  public function getFileTimestamp( array $params ) {
644  $index = $this->getReadIndexFromParams( $params );
645  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
646 
647  return $this->backends[$index]->getFileTimestamp( $realParams );
648  }
649 
650  public function getFileSize( array $params ) {
651  $index = $this->getReadIndexFromParams( $params );
652  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
653 
654  return $this->backends[$index]->getFileSize( $realParams );
655  }
656 
657  public function getFileStat( array $params ) {
658  $index = $this->getReadIndexFromParams( $params );
659  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
660 
661  return $this->backends[$index]->getFileStat( $realParams );
662  }
663 
664  public function getFileXAttributes( array $params ) {
665  $index = $this->getReadIndexFromParams( $params );
666  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
667 
668  return $this->backends[$index]->getFileXAttributes( $realParams );
669  }
670 
671  public function getFileContentsMulti( array $params ) {
672  $index = $this->getReadIndexFromParams( $params );
673  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
674 
675  $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
676 
677  $contents = []; // (path => FSFile) mapping using the proxy backend's name
678  foreach ( $contentsM as $path => $data ) {
679  $contents[$this->unsubstPaths( $path, $this->backends[$index] )] = $data;
680  }
681 
682  return $contents;
683  }
684 
685  public function getFileSha1Base36( array $params ) {
686  $index = $this->getReadIndexFromParams( $params );
687  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
688 
689  return $this->backends[$index]->getFileSha1Base36( $realParams );
690  }
691 
692  public function getFileProps( array $params ) {
693  $index = $this->getReadIndexFromParams( $params );
694  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
695 
696  return $this->backends[$index]->getFileProps( $realParams );
697  }
698 
699  public function streamFile( array $params ) {
700  $index = $this->getReadIndexFromParams( $params );
701  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
702 
703  return $this->backends[$index]->streamFile( $realParams );
704  }
705 
706  public function getLocalReferenceMulti( array $params ) {
707  $index = $this->getReadIndexFromParams( $params );
708  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
709 
710  $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
711 
712  $fsFiles = []; // (path => FSFile) mapping using the proxy backend's name
713  foreach ( $fsFilesM as $path => $fsFile ) {
714  $fsFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $fsFile;
715  }
716 
717  return $fsFiles;
718  }
719 
720  public function getLocalCopyMulti( array $params ) {
721  $index = $this->getReadIndexFromParams( $params );
722  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
723 
724  $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
725 
726  $tempFiles = []; // (path => TempFSFile) mapping using the proxy backend's name
727  foreach ( $tempFilesM as $path => $tempFile ) {
728  $tempFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $tempFile;
729  }
730 
731  return $tempFiles;
732  }
733 
734  public function getFileHttpUrl( array $params ) {
735  $index = $this->getReadIndexFromParams( $params );
736  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
737 
738  return $this->backends[$index]->getFileHttpUrl( $realParams );
739  }
740 
741  public function directoryExists( array $params ) {
742  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
743 
744  return $this->backends[$this->masterIndex]->directoryExists( $realParams );
745  }
746 
747  public function getDirectoryList( array $params ) {
748  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
749 
750  return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
751  }
752 
753  public function getFileList( array $params ) {
754  $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
755 
756  return $this->backends[$this->masterIndex]->getFileList( $realParams );
757  }
758 
759  public function getFeatures() {
760  return $this->backends[$this->masterIndex]->getFeatures();
761  }
762 
763  public function clearCache( array $paths = null ) {
764  foreach ( $this->backends as $backend ) {
765  $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
766  $backend->clearCache( $realPaths );
767  }
768  }
769 
770  public function preloadCache( array $paths ) {
771  $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] );
772  $this->backends[$this->readIndex]->preloadCache( $realPaths );
773  }
774 
775  public function preloadFileStat( array $params ) {
776  $index = $this->getReadIndexFromParams( $params );
777  $realParams = $this->substOpPaths( $params, $this->backends[$index] );
778 
779  return $this->backends[$index]->preloadFileStat( $realParams );
780  }
781 
782  public function getScopedLocksForOps( array $ops, StatusValue $status ) {
783  $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
784  $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
785  // Get the paths to lock from the master backend
786  $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
787  // Get the paths under the proxy backend's name
788  $pbPaths = [
790  $paths[LockManager::LOCK_UW],
791  $this->backends[$this->masterIndex]
792  ),
794  $paths[LockManager::LOCK_EX],
795  $this->backends[$this->masterIndex]
796  )
797  ];
798 
799  // Actually acquire the locks
800  return $this->getScopedFileLocks( $pbPaths, 'mixed', $status );
801  }
802 
807  protected function getReadIndexFromParams( array $params ) {
808  return !empty( $params['latest'] ) ? $this->masterIndex : $this->readIndex;
809  }
810 }
FileBackendMultiWrite\getFileList
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.
Definition: FileBackendMultiWrite.php:753
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:589
FileBackendMultiWrite\getFileXAttributes
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.
Definition: FileBackendMultiWrite.php:664
FileBackend
Base class for all file backend classes (including multi-write backends).
Definition: FileBackend.php:98
FileBackendMultiWrite\hasVolatileSources
hasVolatileSources(array $ops)
Definition: FileBackendMultiWrite.php:534
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:720
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:1436
FileBackendMultiWrite\doPublish
doPublish(array $params)
Definition: FileBackendMultiWrite.php:585
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:692
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:507
FileBackendMultiWrite\directoryExists
directoryExists(array $params)
Check if a directory exists at a given storage path.
Definition: FileBackendMultiWrite.php:741
FileBackendMultiWrite\getFileStat
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
Definition: FileBackendMultiWrite.php:657
FileBackendMultiWrite\doOperationsInternal
doOperationsInternal(array $ops, array $opts)
Definition: FileBackendMultiWrite.php:141
FileBackendMultiWrite\getFileSize
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
Definition: FileBackendMultiWrite.php:650
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:706
FileBackendMultiWrite\clearCache
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
Definition: FileBackendMultiWrite.php:763
FileBackendMultiWrite\preloadFileStat
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
Definition: FileBackendMultiWrite.php:775
FileBackendMultiWrite\doDirectoryOp
doDirectoryOp( $method, array $params)
Definition: FileBackendMultiWrite.php:598
FileBackendMultiWrite\fileExists
fileExists(array $params)
Check if a file exists at a storage path in the backend.
Definition: FileBackendMultiWrite.php:636
FileBackendMultiWrite\doQuickOperationsInternal
doQuickOperationsInternal(array $ops, array $opts)
Definition: FileBackendMultiWrite.php:544
FileBackendMultiWrite\getFeatures
getFeatures()
Get the a bitfield of extra features supported by the backend medium Stable to override.
Definition: FileBackendMultiWrite.php:759
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:685
FileBackendMultiWrite\concatenate
concatenate(array $params)
Concatenate a list of storage files into a single file system file.
Definition: FileBackendMultiWrite.php:625
FileBackendStore
Base class for all backends using particular storage medium.
Definition: FileBackendStore.php:41
FileBackend\newStatus
newStatus(... $args)
Yields the result of the status wrapper callback on either:
Definition: FileBackend.php:1695
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:747
FileBackendMultiWrite\getFileHttpUrl
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.
Definition: FileBackendMultiWrite.php:734
FileBackendMultiWrite\$asyncWrites
bool $asyncWrites
Definition: FileBackendMultiWrite.php:60
FileBackendMultiWrite\doPrepare
doPrepare(array $params)
Definition: FileBackendMultiWrite.php:577
FileBackendMultiWrite\fileStoragePathsForOps
fileStoragePathsForOps(array $ops)
Get a list of file storage paths to read or write for a list of operations.
Definition: FileBackendMultiWrite.php:441
FileBackendMultiWrite\substOpPaths
substOpPaths(array $ops, FileBackendStore $backend)
Same as substOpBatchPaths() but for a single operation.
Definition: FileBackendMultiWrite.php:494
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:671
$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:807
FileBackendMultiWrite\consistencyCheck
consistencyCheck(array $paths)
Check that a set of files are consistent across all internal backends.
Definition: FileBackendMultiWrite.php:233
FileBackendMultiWrite\unsubstPaths
unsubstPaths( $paths, FileBackendStore $backend)
Substitute the backend of internal storage paths with the proxy backend's name.
Definition: FileBackendMultiWrite.php:522
FileBackendMultiWrite\getScopedLocksForOps
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.
Definition: FileBackendMultiWrite.php:782
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:321
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 a callable update.
Definition: DeferredUpdates.php:145
FileBackendMultiWrite\doSecure
doSecure(array $params)
Definition: FileBackendMultiWrite.php:581
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:699
FileBackendMultiWrite\preloadCache
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
Definition: FileBackendMultiWrite.php:770
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:349
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:643
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:472
FileBackendMultiWrite\$readIndex
int $readIndex
Index of read affinity backend.
Definition: FileBackendMultiWrite.php:52