24 use Wikimedia\Timestamp\ConvertibleTimestamp;
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';
105 foreach ( $config[
'backends'] as $index => $config ) {
106 $name = $config[
'name'];
107 if ( isset( $namesUsed[
$name] ) ) {
108 throw new LogicException(
"Two or more backends defined with the name $name." );
110 $namesUsed[
$name] = 1;
112 unset( $config[
'readOnly'] );
113 unset( $config[
'fileJournal'] );
114 unset( $config[
'lockManager'] );
116 if ( !empty( $config[
'isMultiMaster'] ) ) {
117 if ( $this->masterIndex >= 0 ) {
118 throw new LogicException(
'More than one master backend defined.' );
120 $this->masterIndex = $index;
123 if ( !empty( $config[
'readAffinity'] ) ) {
124 $this->readIndex = $index;
127 if ( !isset( $config[
'class'] ) ) {
128 throw new InvalidArgumentException(
'No class given for a backend config.' );
130 $class = $config[
'class'];
131 $this->backends[$index] =
new $class( $config );
133 if ( $this->masterIndex < 0 ) {
134 throw new LogicException(
'No master backend defined.' );
136 if ( $this->readIndex < 0 ) {
147 if ( empty( $opts[
'nonLocking'] ) ) {
156 $opts[
'preserveCache'] =
true;
166 if ( !$syncStatus->isOK() ) {
167 $this->logger->error(
172 $this->autoResync ===
false ||
173 !$this->
resyncFiles( $relevantPaths, $this->autoResync )->isOK()
182 $masterStatus = $mbe->doOperations( $realOps, $opts );
183 $status->merge( $masterStatus );
187 if ( $masterStatus->isOK() && $masterStatus->successCount > 0 ) {
188 foreach ( $this->backends as $index => $backend ) {
189 if ( $index === $this->masterIndex ) {
197 function () use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
198 $this->logger->error(
199 "'{$backend->getName()}' async replication; paths: " .
202 $backend->doOperations( $realOps, $opts );
206 $this->logger->error(
207 "'{$backend->getName()}' sync replication; paths: " .
210 $status->merge( $backend->doOperations( $realOps, $opts ) );
217 $status->success = $masterStatus->success;
218 $status->successCount = $masterStatus->successCount;
219 $status->failCount = $masterStatus->failCount;
235 if ( $this->syncChecks == 0 || count( $this->backends ) <= 1 ) {
240 foreach ( $this->backends as $backend ) {
241 $realPaths = $this->
substPaths( $paths, $backend );
242 $backend->preloadFileStat( [
'srcs' => $realPaths,
'latest' =>
true ] );
245 foreach ( $paths as
$path ) {
246 $params = [
'src' =>
$path,
'latest' =>
true ];
249 $masterParams = $this->
substOpPaths( $params, $masterBackend );
250 $masterStat = $masterBackend->getFileStat( $masterParams );
251 if ( $masterStat === self::STAT_ERROR ) {
255 if ( $this->syncChecks & self::CHECK_SHA1 ) {
256 $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
257 if ( ( $masterSha1 !==
false ) !== (
bool)$masterStat ) {
266 foreach ( $this->backends as $index => $cloneBackend ) {
267 if ( $index === $this->masterIndex ) {
272 $cloneParams = $this->
substOpPaths( $params, $cloneBackend );
273 $cloneStat = $cloneBackend->getFileStat( $cloneParams );
281 ( $this->syncChecks & self::CHECK_SIZE ) &&
282 $cloneStat[
'size'] !== $masterStat[
'size']
287 ( $this->syncChecks & self::CHECK_TIME ) &&
289 ConvertibleTimestamp::convert( TS_UNIX, $masterStat[
'mtime'] ) -
290 ConvertibleTimestamp::convert( TS_UNIX, $cloneStat[
'mtime'] )
296 ( $this->syncChecks & self::CHECK_SHA1 ) &&
297 $cloneBackend->getFileSha1Base36( $cloneParams ) !== $masterSha1
323 if ( count( $this->backends ) <= 1 ) {
327 foreach ( $paths as
$path ) {
328 foreach ( $this->backends as $backend ) {
329 $realPath = $this->
substPaths( $path, $backend );
330 if ( !$backend->isPathUsableInternal( $realPath ) ) {
353 foreach ( $paths as
$path ) {
354 $params = [
'src' =>
$path,
'latest' =>
true ];
357 $masterParams = $this->
substOpPaths( $params, $masterBackend );
358 $masterPath = $masterParams[
'src'];
359 $masterStat = $masterBackend->getFileStat( $masterParams );
360 if ( $masterStat === self::STAT_ERROR ) {
362 $this->logger->error(
"$fname: file '$masterPath' is not available" );
365 $masterSha1 = $masterBackend->getFileSha1Base36( $masterParams );
366 if ( ( $masterSha1 !==
false ) !== (
bool)$masterStat ) {
368 $this->logger->error(
"$fname: file '$masterPath' hash does not match stat" );
373 foreach ( $this->backends as $index => $cloneBackend ) {
374 if ( $index === $this->masterIndex ) {
379 $cloneParams = $this->
substOpPaths( $params, $cloneBackend );
380 $clonePath = $cloneParams[
'src'];
381 $cloneStat = $cloneBackend->getFileStat( $cloneParams );
382 if ( $cloneStat === self::STAT_ERROR ) {
384 $this->logger->error(
"$fname: file '$clonePath' is not available" );
387 $cloneSha1 = $cloneBackend->getFileSha1Base36( $cloneParams );
388 if ( ( $cloneSha1 !==
false ) !== (
bool)$cloneStat ) {
390 $this->logger->error(
"$fname: file '$clonePath' hash does not match stat" );
394 if ( $masterSha1 === $cloneSha1 ) {
396 $this->logger->debug(
"$fname: file '$clonePath' matches '$masterPath'" );
397 } elseif ( $masterSha1 !==
false ) {
400 $resyncMode ===
'conservative' &&
402 $cloneStat[
'mtime'] > $masterStat[
'mtime']
408 $fsFile = $masterBackend->getLocalReference( $masterParams );
409 $status->merge( $cloneBackend->quickStore( [
414 } elseif ( $masterStat ===
false ) {
416 if ( $resyncMode ===
'conservative' ) {
421 $status->merge( $cloneBackend->quickDelete( [
'src' => $clonePath ] ) );
442 foreach ( $ops as $op ) {
443 if ( isset( $op[
'src'] ) ) {
446 if ( empty( $op[
'ignoreMissingSource'] )
447 || $this->
fileExists( [
'src' => $op[
'src'] ] )
449 $paths[] = $op[
'src'];
452 if ( isset( $op[
'srcs'] ) ) {
453 $paths = array_merge( $paths, $op[
'srcs'] );
455 if ( isset( $op[
'dst'] ) ) {
456 $paths[] = $op[
'dst'];
460 return array_values( array_unique( array_filter( $paths,
'FileBackend::isStoragePath' ) ) );
473 foreach ( $ops as $op ) {
475 foreach ( [
'src',
'srcs',
'dst',
'dir' ] as $par ) {
476 if ( isset( $newOp[$par] ) ) {
477 $newOp[$par] = $this->
substPaths( $newOp[$par], $backend );
508 '!^mwstore://' . preg_quote( $this->name,
'!' ) .
'/!',
523 '!^mwstore://' . preg_quote( $backend->
getName(),
'!' ) .
'/!',
534 foreach ( $ops as $op ) {
535 if ( $op[
'op'] ===
'store' && !isset( $op[
'srcRef'] ) ) {
546 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
548 $status->merge( $masterStatus );
550 foreach ( $this->backends as $index => $backend ) {
551 if ( $index === $this->masterIndex ) {
558 function () use ( $backend, $realOps ) {
559 $backend->doQuickOperations( $realOps );
563 $status->merge( $backend->doQuickOperations( $realOps ) );
569 $status->success = $masterStatus->success;
570 $status->successCount = $masterStatus->successCount;
571 $status->failCount = $masterStatus->failCount;
600 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
602 $status->merge( $masterStatus );
604 foreach ( $this->backends as $index => $backend ) {
605 if ( $index === $this->masterIndex ) {
610 if ( $this->asyncWrites ) {
612 function () use ( $backend, $method, $realParams ) {
613 $backend->$method( $realParams );
617 $status->merge( $backend->$method( $realParams ) );
628 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
637 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
639 return $this->backends[$index]->fileExists( $realParams );
644 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
646 return $this->backends[$index]->getFileTimestamp( $realParams );
651 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
653 return $this->backends[$index]->getFileSize( $realParams );
658 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
660 return $this->backends[$index]->getFileStat( $realParams );
665 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
667 return $this->backends[$index]->getFileXAttributes( $realParams );
672 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
674 $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams );
677 foreach ( $contentsM as
$path => $data ) {
686 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
688 return $this->backends[$index]->getFileSha1Base36( $realParams );
693 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
695 return $this->backends[$index]->getFileProps( $realParams );
700 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
702 return $this->backends[$index]->streamFile( $realParams );
707 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
709 $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams );
712 foreach ( $fsFilesM as
$path => $fsFile ) {
721 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
723 $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams );
726 foreach ( $tempFilesM as
$path => $tempFile ) {
735 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
737 return $this->backends[$index]->getFileHttpUrl( $realParams );
741 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
747 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
753 $realParams = $this->
substOpPaths( $params, $this->backends[$this->masterIndex] );
763 foreach ( $this->backends as $backend ) {
764 $realPaths = is_array( $paths ) ? $this->
substPaths( $paths, $backend ) :
null;
765 $backend->clearCache( $realPaths );
770 $realPaths = $this->
substPaths( $paths, $this->backends[$this->readIndex] );
776 $realParams = $this->
substOpPaths( $params, $this->backends[$index] );
778 return $this->backends[$index]->preloadFileStat( $realParams );
782 $realOps = $this->
substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
785 $paths = $this->backends[
$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
790 $this->backends[$this->masterIndex]
794 $this->backends[$this->masterIndex]