MediaWiki REL1_39
FileBackendMultiWrite.php
Go to the documentation of this file.
1<?php
24use 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
112 unset( $beConfig['readOnly'] ); // use proxy backend setting
113 unset( $beConfig['lockManager'] ); // lock under proxy backend
114 $beConfig['domainId'] = $this->domainId; // use the proxy backend wiki ID
115 $beConfig['logger'] = $this->logger; // use the proxy backend logger
116 if ( !empty( $beConfig['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 }
122 if ( !empty( $beConfig['readAffinity'] ) ) {
123 $this->readIndex = $index; // prefer this for reads
124 }
125 // Create sub-backend object
126 if ( !isset( $beConfig['class'] ) ) {
127 throw new InvalidArgumentException( 'No class given for a backend config.' );
128 }
129 $class = $beConfig['class'];
130 $this->backends[$index] = new $class( $beConfig );
131 }
132 if ( $this->masterIndex < 0 ) { // need backends and must have a master
133 throw new LogicException( 'No master backend defined.' );
134 }
135 if ( $this->readIndex < 0 ) {
136 $this->readIndex = $this->masterIndex; // default
137 }
138 }
139
140 final protected function doOperationsInternal( array $ops, array $opts ) {
141 $status = $this->newStatus();
142
143 $fname = __METHOD__;
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 "$fname: 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
196 DeferredUpdates::addCallableUpdate(
197 function () use (
198 $backend, $realOps, $opts, $scopeLock, $relevantPaths, $fname
199 ) {
200 $this->logger->debug(
201 "$fname: '{$backend->getName()}' async replication; paths: " .
202 FormatJson::encode( $relevantPaths )
203 );
204 $backend->doOperations( $realOps, $opts );
205 }
206 );
207 } else {
208 $this->logger->debug(
209 "$fname: '{$backend->getName()}' sync replication; paths: " .
210 FormatJson::encode( $relevantPaths )
211 );
212 $status->merge( $backend->doOperations( $realOps, $opts ) );
213 }
214 }
215 }
216 // Make 'success', 'successCount', and 'failCount' fields reflect
217 // the overall operation, rather than all the batches for each backend.
218 // Do this by only using success values from the master backend's batch.
219 $status->success = $masterStatus->success;
220 $status->successCount = $masterStatus->successCount;
221 $status->failCount = $masterStatus->failCount;
222
223 return $status;
224 }
225
235 public function consistencyCheck( array $paths ) {
236 $status = $this->newStatus();
237 if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
238 return $status; // skip checks
239 }
240
241 // Preload all of the stat info in as few round trips as possible
242 foreach ( $this->backends as $backend ) {
243 $realPaths = $this->substPaths( $paths, $backend );
244 $backend->preloadFileStat( [ 'srcs' => $realPaths, 'latest' => true ] );
245 }
246
247 foreach ( $paths as $path ) {
248 $params = [ 'src' => $path, 'latest' => true ];
249 // Get the state of the file on the master backend
250 $masterBackend = $this->backends[$this->masterIndex];
251 $masterParams = $this->substOpPaths( $params, $masterBackend );
252 $masterStat = $masterBackend->getFileStat( $masterParams );
253 if ( $masterStat === self::STAT_ERROR ) {
254 $status->fatal( 'backend-fail-stat', $path );
255 continue;
256 }
257 if ( $this->syncChecks & self::CHECK_SHA1 ) {
258 $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
259 if ( ( $masterSha1 !== false ) !== (bool)$masterStat ) {
260 $status->fatal( 'backend-fail-hash', $path );
261 continue;
262 }
263 } else {
264 $masterSha1 = null; // unused
265 }
266
267 // Check if all clone backends agree with the master...
268 foreach ( $this->backends as $index => $cloneBackend ) {
269 if ( $index === $this->masterIndex ) {
270 continue; // master
271 }
272
273 // Get the state of the file on the clone backend
274 $cloneParams = $this->substOpPaths( $params, $cloneBackend );
275 $cloneStat = $cloneBackend->getFileStat( $cloneParams );
276
277 if ( $masterStat ) {
278 // File exists in the master backend
279 if ( !$cloneStat ) {
280 // File is missing from the clone backend
281 $status->fatal( 'backend-fail-synced', $path );
282 } elseif (
283 ( $this->syncChecks & self::CHECK_SIZE ) &&
284 $cloneStat['size'] !== $masterStat['size']
285 ) {
286 // File in the clone backend is different
287 $status->fatal( 'backend-fail-synced', $path );
288 } elseif (
289 ( $this->syncChecks & self::CHECK_TIME ) &&
290 abs(
291 (int)ConvertibleTimestamp::convert( TS_UNIX, $masterStat['mtime'] ) -
292 (int)ConvertibleTimestamp::convert( TS_UNIX, $cloneStat['mtime'] )
293 ) > 30
294 ) {
295 // File in the clone backend is significantly newer or older
296 $status->fatal( 'backend-fail-synced', $path );
297 } elseif (
298 ( $this->syncChecks & self::CHECK_SHA1 ) &&
299 $cloneBackend->getFileSha1Base36( $cloneParams ) !== $masterSha1
300 ) {
301 // File in the clone backend is different
302 $status->fatal( 'backend-fail-synced', $path );
303 }
304 } else {
305 // File does not exist in the master backend
306 if ( $cloneStat ) {
307 // Stray file exists in the clone backend
308 $status->fatal( 'backend-fail-synced', $path );
309 }
310 }
311 }
312 }
313
314 return $status;
315 }
316
323 public function accessibilityCheck( array $paths ) {
324 $status = $this->newStatus();
325 if ( count( $this->backends ) <= 1 ) {
326 return $status; // skip checks
327 }
328
329 foreach ( $paths as $path ) {
330 foreach ( $this->backends as $backend ) {
331 $realPath = $this->substPaths( $path, $backend );
332 if ( !$backend->isPathUsableInternal( $realPath ) ) {
333 $status->fatal( 'backend-fail-usable', $path );
334 }
335 }
336 }
337
338 return $status;
339 }
340
351 public function resyncFiles( array $paths, $resyncMode = true ) {
352 $status = $this->newStatus();
353
354 $fname = __METHOD__;
355 foreach ( $paths as $path ) {
356 $params = [ 'src' => $path, 'latest' => true ];
357 // Get the state of the file on the master backend
358 $masterBackend = $this->backends[$this->masterIndex];
359 $masterParams = $this->substOpPaths( $params, $masterBackend );
360 $masterPath = $masterParams['src'];
361 $masterStat = $masterBackend->getFileStat( $masterParams );
362 if ( $masterStat === self::STAT_ERROR ) {
363 $status->fatal( 'backend-fail-stat', $path );
364 $this->logger->error( "$fname: file '$masterPath' is not available" );
365 continue;
366 }
367 $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
368 if ( ( $masterSha1 !== false ) !== (bool)$masterStat ) {
369 $status->fatal( 'backend-fail-hash', $path );
370 $this->logger->error( "$fname: file '$masterPath' hash does not match stat" );
371 continue;
372 }
373
374 // Check of all clone backends agree with the master...
375 foreach ( $this->backends as $index => $cloneBackend ) {
376 if ( $index === $this->masterIndex ) {
377 continue; // master
378 }
379
380 // Get the state of the file on the clone backend
381 $cloneParams = $this->substOpPaths( $params, $cloneBackend );
382 $clonePath = $cloneParams['src'];
383 $cloneStat = $cloneBackend->getFileStat( $cloneParams );
384 if ( $cloneStat === self::STAT_ERROR ) {
385 $status->fatal( 'backend-fail-stat', $path );
386 $this->logger->error( "$fname: file '$clonePath' is not available" );
387 continue;
388 }
389 $cloneSha1 = $cloneBackend->getFileSha1Base36( $cloneParams );
390 if ( ( $cloneSha1 !== false ) !== (bool)$cloneStat ) {
391 $status->fatal( 'backend-fail-hash', $path );
392 $this->logger->error( "$fname: file '$clonePath' hash does not match stat" );
393 continue;
394 }
395
396 if ( $masterSha1 === $cloneSha1 ) {
397 // File is either the same in both backends or absent from both backends
398 $this->logger->debug( "$fname: file '$clonePath' matches '$masterPath'" );
399 } elseif ( $masterSha1 !== false ) {
400 // File is either missing from or different in the clone backend
401 if (
402 $resyncMode === 'conservative' &&
403 $cloneStat &&
404 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
405 $cloneStat['mtime'] > $masterStat['mtime']
406 ) {
407 // Do not replace files with older ones; reduces the risk of data loss
408 $status->fatal( 'backend-fail-synced', $path );
409 } else {
410 // Copy the master backend file to the clone backend in overwrite mode
411 $fsFile = $masterBackend->getLocalReference( $masterParams );
412 $status->merge( $cloneBackend->quickStore( [
413 'src' => $fsFile,
414 'dst' => $clonePath
415 ] ) );
416 }
417 } elseif ( $masterStat === false ) {
418 // Stray file exists in the clone backend
419 if ( $resyncMode === 'conservative' ) {
420 // Do not delete stray files; reduces the risk of data loss
421 $status->fatal( 'backend-fail-synced', $path );
422 $this->logger->error( "$fname: not allowed to delete file '$clonePath'" );
423 } else {
424 // Delete the stay file from the clone backend
425 $status->merge( $cloneBackend->quickDelete( [ 'src' => $clonePath ] ) );
426 }
427 }
428 }
429 }
430
431 if ( !$status->isOK() ) {
432 $this->logger->error( "$fname: failed to resync: " . FormatJson::encode( $paths ) );
433 }
434
435 return $status;
436 }
437
444 protected function fileStoragePathsForOps( array $ops ) {
445 $paths = [];
446 foreach ( $ops as $op ) {
447 if ( isset( $op['src'] ) ) {
448 // For things like copy/move/delete with "ignoreMissingSource" and there
449 // is no source file, nothing should happen and there should be no errors.
450 if ( empty( $op['ignoreMissingSource'] )
451 || $this->fileExists( [ 'src' => $op['src'] ] )
452 ) {
453 $paths[] = $op['src'];
454 }
455 }
456 if ( isset( $op['srcs'] ) ) {
457 $paths = array_merge( $paths, $op['srcs'] );
458 }
459 if ( isset( $op['dst'] ) ) {
460 $paths[] = $op['dst'];
461 }
462 }
463
464 return array_values( array_unique( array_filter( $paths, [ FileBackend::class, 'isStoragePath' ] ) ) );
465 }
466
475 protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
476 $newOps = []; // operations
477 foreach ( $ops as $op ) {
478 $newOp = $op; // operation
479 foreach ( [ 'src', 'srcs', 'dst', 'dir' ] as $par ) {
480 if ( isset( $newOp[$par] ) ) { // string or array
481 $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
482 }
483 }
484 $newOps[] = $newOp;
485 }
486
487 return $newOps;
488 }
489
497 protected function substOpPaths( array $ops, FileBackendStore $backend ) {
498 $newOps = $this->substOpBatchPaths( [ $ops ], $backend );
499
500 return $newOps[0];
501 }
502
510 protected function substPaths( $paths, FileBackendStore $backend ) {
511 return preg_replace(
512 '!^mwstore://' . preg_quote( $this->name, '!' ) . '/!',
513 StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
514 $paths // string or array
515 );
516 }
517
525 protected function unsubstPaths( $paths, FileBackendStore $backend ) {
526 return preg_replace(
527 '!^mwstore://' . preg_quote( $backend->getName(), '!' ) . '/!',
528 StringUtils::escapeRegexReplacement( "mwstore://{$this->name}/" ),
529 $paths // string or array
530 );
531 }
532
537 protected function hasVolatileSources( array $ops ) {
538 foreach ( $ops as $op ) {
539 if ( $op['op'] === 'store' && !isset( $op['srcRef'] ) ) {
540 return true; // source file might be deleted anytime after do*Operations()
541 }
542 }
543
544 return false;
545 }
546
547 protected function doQuickOperationsInternal( array $ops, array $opts ) {
548 $status = $this->newStatus();
549 // Do the operations on the master backend; setting StatusValue fields
550 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
551 $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
552 $status->merge( $masterStatus );
553 // Propagate the operations to the clone backends...
554 foreach ( $this->backends as $index => $backend ) {
555 if ( $index === $this->masterIndex ) {
556 continue; // done already
557 }
558
559 $realOps = $this->substOpBatchPaths( $ops, $backend );
560 if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
561 DeferredUpdates::addCallableUpdate(
562 static function () use ( $backend, $realOps ) {
563 $backend->doQuickOperations( $realOps );
564 }
565 );
566 } else {
567 $status->merge( $backend->doQuickOperations( $realOps ) );
568 }
569 }
570 // Make 'success', 'successCount', and 'failCount' fields reflect
571 // the overall operation, rather than all the batches for each backend.
572 // Do this by only using success values from the master backend's batch.
573 $status->success = $masterStatus->success;
574 $status->successCount = $masterStatus->successCount;
575 $status->failCount = $masterStatus->failCount;
576
577 return $status;
578 }
579
580 protected function doPrepare( array $params ) {
581 return $this->doDirectoryOp( 'prepare', $params );
582 }
583
584 protected function doSecure( array $params ) {
585 return $this->doDirectoryOp( 'secure', $params );
586 }
587
588 protected function doPublish( array $params ) {
589 return $this->doDirectoryOp( 'publish', $params );
590 }
591
592 protected function doClean( array $params ) {
593 return $this->doDirectoryOp( 'clean', $params );
594 }
595
601 protected function doDirectoryOp( $method, array $params ) {
602 $status = $this->newStatus();
603
604 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
605 $masterStatus = $this->backends[$this->masterIndex]->$method( $realParams );
606 $status->merge( $masterStatus );
607
608 foreach ( $this->backends as $index => $backend ) {
609 if ( $index === $this->masterIndex ) {
610 continue; // already done
611 }
612
613 $realParams = $this->substOpPaths( $params, $backend );
614 if ( $this->asyncWrites ) {
615 DeferredUpdates::addCallableUpdate(
616 static function () use ( $backend, $method, $realParams ) {
617 $backend->$method( $realParams );
618 }
619 );
620 } else {
621 $status->merge( $backend->$method( $realParams ) );
622 }
623 }
624
625 return $status;
626 }
627
628 public function concatenate( array $params ) {
629 $status = $this->newStatus();
630 // We are writing to an FS file, so we don't need to do this per-backend
631 $index = $this->getReadIndexFromParams( $params );
632 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
633
634 $status->merge( $this->backends[$index]->concatenate( $realParams ) );
635
636 return $status;
637 }
638
639 public function fileExists( array $params ) {
640 $index = $this->getReadIndexFromParams( $params );
641 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
642
643 return $this->backends[$index]->fileExists( $realParams );
644 }
645
646 public function getFileTimestamp( array $params ) {
647 $index = $this->getReadIndexFromParams( $params );
648 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
649
650 return $this->backends[$index]->getFileTimestamp( $realParams );
651 }
652
653 public function getFileSize( array $params ) {
654 $index = $this->getReadIndexFromParams( $params );
655 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
656
657 return $this->backends[$index]->getFileSize( $realParams );
658 }
659
660 public function getFileStat( array $params ) {
661 $index = $this->getReadIndexFromParams( $params );
662 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
663
664 return $this->backends[$index]->getFileStat( $realParams );
665 }
666
667 public function getFileXAttributes( array $params ) {
668 $index = $this->getReadIndexFromParams( $params );
669 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
670
671 return $this->backends[$index]->getFileXAttributes( $realParams );
672 }
673
674 public function getFileContentsMulti( array $params ) {
675 $index = $this->getReadIndexFromParams( $params );
676 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
677
678 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
679
680 $contents = []; // (path => FSFile) mapping using the proxy backend's name
681 foreach ( $contentsM as $path => $data ) {
682 $contents[$this->unsubstPaths( $path, $this->backends[$index] )] = $data;
683 }
684
685 return $contents;
686 }
687
688 public function getFileSha1Base36( array $params ) {
689 $index = $this->getReadIndexFromParams( $params );
690 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
691
692 return $this->backends[$index]->getFileSha1Base36( $realParams );
693 }
694
695 public function getFileProps( array $params ) {
696 $index = $this->getReadIndexFromParams( $params );
697 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
698
699 return $this->backends[$index]->getFileProps( $realParams );
700 }
701
702 public function streamFile( array $params ) {
703 $index = $this->getReadIndexFromParams( $params );
704 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
705
706 return $this->backends[$index]->streamFile( $realParams );
707 }
708
709 public function getLocalReferenceMulti( array $params ) {
710 $index = $this->getReadIndexFromParams( $params );
711 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
712
713 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
714
715 $fsFiles = []; // (path => FSFile) mapping using the proxy backend's name
716 foreach ( $fsFilesM as $path => $fsFile ) {
717 $fsFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $fsFile;
718 }
719
720 return $fsFiles;
721 }
722
723 public function getLocalCopyMulti( array $params ) {
724 $index = $this->getReadIndexFromParams( $params );
725 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
726
727 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
728
729 $tempFiles = []; // (path => TempFSFile) mapping using the proxy backend's name
730 foreach ( $tempFilesM as $path => $tempFile ) {
731 $tempFiles[$this->unsubstPaths( $path, $this->backends[$index] )] = $tempFile;
732 }
733
734 return $tempFiles;
735 }
736
737 public function getFileHttpUrl( array $params ) {
738 $index = $this->getReadIndexFromParams( $params );
739 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
740
741 return $this->backends[$index]->getFileHttpUrl( $realParams );
742 }
743
744 public function directoryExists( array $params ) {
745 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
746
747 return $this->backends[$this->masterIndex]->directoryExists( $realParams );
748 }
749
750 public function getDirectoryList( array $params ) {
751 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
752
753 return $this->backends[$this->masterIndex]->getDirectoryList( $realParams );
754 }
755
756 public function getFileList( array $params ) {
757 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
758
759 return $this->backends[$this->masterIndex]->getFileList( $realParams );
760 }
761
762 public function getFeatures() {
763 return $this->backends[$this->masterIndex]->getFeatures();
764 }
765
766 public function clearCache( array $paths = null ) {
767 foreach ( $this->backends as $backend ) {
768 $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
769 $backend->clearCache( $realPaths );
770 }
771 }
772
773 public function preloadCache( array $paths ) {
774 $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] );
775 $this->backends[$this->readIndex]->preloadCache( $realPaths );
776 }
777
778 public function preloadFileStat( array $params ) {
779 $index = $this->getReadIndexFromParams( $params );
780 $realParams = $this->substOpPaths( $params, $this->backends[$index] );
781
782 return $this->backends[$index]->preloadFileStat( $realParams );
783 }
784
785 public function getScopedLocksForOps( array $ops, StatusValue $status ) {
786 $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
787 $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
788 // Get the paths to lock from the master backend
789 $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
790 // Get the paths under the proxy backend's name
791 $pbPaths = [
792 LockManager::LOCK_UW => $this->unsubstPaths(
793 $paths[LockManager::LOCK_UW],
794 $this->backends[$this->masterIndex]
795 ),
796 LockManager::LOCK_EX => $this->unsubstPaths(
797 $paths[LockManager::LOCK_EX],
798 $this->backends[$this->masterIndex]
799 )
800 ];
801
802 // Actually acquire the locks
803 return $this->getScopedFileLocks( $pbPaths, 'mixed', $status );
804 }
805
810 protected function getReadIndexFromParams( array $params ) {
811 return !empty( $params['latest'] ) ? $this->masterIndex : $this->readIndex;
812 }
813}
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.
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).
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
return true
Definition router.php:92