Reduced max TempFSFile::factory() attempts since it uses 12 hex chars.
[lhc/web/wiklou.git] / includes / filebackend / FileBackendMultiWrite.php
index e9136d5..9efa0db 100644 (file)
@@ -43,7 +43,10 @@ class FileBackendMultiWrite extends FileBackend {
        /** @var Array Prioritized list of FileBackendStore objects */
        protected $backends = array(); // array of (backend index => backends)
        protected $masterIndex = -1; // integer; index of master backend
-       protected $syncChecks = 0; // integer bitfield
+       protected $syncChecks = 0; // integer; bitfield
+       /** @var Array */
+       protected $noPushDirConts = array();
+       protected $noPushQuickOps = false; // boolean
 
        /* Possible internal backend consistency checks */
        const CHECK_SIZE = 1;
@@ -55,26 +58,38 @@ class FileBackendMultiWrite extends FileBackend {
         * Locking, journaling, and read-only checks are handled by the proxy backend.
         *
         * Additional $config params include:
-        *   - backends   : Array of backend config and multi-backend settings.
-        *                  Each value is the config used in the constructor of a
-        *                  FileBackendStore class, but with these additional settings:
-        *                    - class         : The name of the backend class
-        *                    - isMultiMaster : This must be set for one backend.
-        *                    - template:     : If given a backend name, this will use
-        *                                      the config of that backend as a template.
-        *                                      Values specified here take precedence.
-        *   - syncChecks : Integer bitfield of internal backend sync checks to perform.
-        *                  Possible bits include the FileBackendMultiWrite::CHECK_* constants.
-        *                  There are constants for SIZE, TIME, and SHA1.
-        *                  The checks are done before allowing any file operations.
+        *   - backends       : Array of backend config and multi-backend settings.
+        *                      Each value is the config used in the constructor of a
+        *                          FileBackendStore class, but with these additional settings:
+        *                        - class         : The name of the backend class
+        *                        - isMultiMaster : This must be set for one backend.
+        *                        - template:     : If given a backend name, this will use
+        *                                          the config of that backend as a template.
+        *                                          Values specified here take precedence.
+        *   - syncChecks     : Integer bitfield of internal backend sync checks to perform.
+        *                      Possible bits include the FileBackendMultiWrite::CHECK_* constants.
+        *                      There are constants for SIZE, TIME, and SHA1.
+        *                      The checks are done before allowing any file operations.
+        *   - noPushQuickOps : (hack) Only apply doQuickOperations() to the master backend.
+        *   - noPushDirConts : (hack) Only apply directory functions to the master backend.
+        *
         * @param $config Array
         * @throws MWException
         */
        public function __construct( array $config ) {
                parent::__construct( $config );
-               $namesUsed = array();
+               $this->syncChecks = isset( $config['syncChecks'] )
+                       ? $config['syncChecks']
+                       : self::CHECK_SIZE;
+               $this->noPushQuickOps = isset( $config['noPushQuickOps'] )
+                       ? $config['noPushQuickOps']
+                       : false;
+               $this->noPushDirConts = isset( $config['noPushDirConts'] )
+                       ? $config['noPushDirConts']
+                       : array();
                // Construct backends here rather than via registration
                // to keep these backends hidden from outside the proxy.
+               $namesUsed = array();
                foreach ( $config['backends'] as $index => $config ) {
                        if ( isset( $config['template'] ) ) {
                                // Config is just a modified version of a registered backend's.
@@ -108,9 +123,6 @@ class FileBackendMultiWrite extends FileBackend {
                if ( $this->masterIndex < 0 ) { // need backends and must have a master
                        throw new MWException( 'No master backend defined.' );
                }
-               $this->syncChecks = isset( $config['syncChecks'] )
-                       ? $config['syncChecks']
-                       : self::CHECK_SIZE;
        }
 
        /**
@@ -139,6 +151,7 @@ class FileBackendMultiWrite extends FileBackend {
                }
                // Clear any cache entries (after locks acquired)
                $this->clearCache();
+               $opts['preserveCache'] = true; // only locked files are cached
                // Do a consistency check to see if the backends agree
                $status->merge( $this->consistencyCheck( $this->fileStoragePathsForOps( $ops ) ) );
                if ( !$status->isOK() ) {
@@ -147,11 +160,14 @@ class FileBackendMultiWrite extends FileBackend {
                // Actually attempt the operation batch on the master backend...
                $masterStatus = $mbe->doOperations( $realOps, $opts );
                $status->merge( $masterStatus );
-               // Propagate the operations to the clone backends...
-               foreach ( $this->backends as $index => $backend ) {
-                       if ( $index !== $this->masterIndex ) { // not done already
-                               $realOps = $this->substOpBatchPaths( $ops, $backend );
-                               $status->merge( $backend->doOperations( $realOps, $opts ) );
+               // Propagate the operations to the clone backends if there were no fatal errors.
+               // If $ops only had one operation, this might avoid backend inconsistencies.
+               if ( !count( $masterStatus->getErrorsArray() ) ) {
+                       foreach ( $this->backends as $index => $backend ) {
+                               if ( $index !== $this->masterIndex ) { // not done already
+                                       $realOps = $this->substOpBatchPaths( $ops, $backend );
+                                       $status->merge( $backend->doOperations( $realOps, $opts ) );
+                               }
                        }
                }
                // Make 'success', 'successCount', and 'failCount' fields reflect
@@ -167,7 +183,7 @@ class FileBackendMultiWrite extends FileBackend {
        /**
         * Check that a set of files are consistent across all internal backends
         *
-        * @param $paths Array
+        * @param $paths Array List of storage paths
         * @return Status
         */
        public function consistencyCheck( array $paths ) {
@@ -183,7 +199,7 @@ class FileBackendMultiWrite extends FileBackend {
                        // Stat the file on the 'master' backend
                        $mStat = $mBackend->getFileStat( $mParams );
                        if ( $this->syncChecks & self::CHECK_SHA1 ) {
-                               $mSha1 = $mBackend->getFileSha1( $mParams );
+                               $mSha1 = $mBackend->getFileSha1Base36( $mParams );
                        } else {
                                $mSha1 = false;
                        }
@@ -215,7 +231,7 @@ class FileBackendMultiWrite extends FileBackend {
                                                }
                                        }
                                        if ( $this->syncChecks & self::CHECK_SHA1 ) {
-                                               if ( $cBackend->getFileSha1( $cParams ) !== $mSha1 ) { // wrong SHA1
+                                               if ( $cBackend->getFileSha1Base36( $cParams ) !== $mSha1 ) { // wrong SHA1
                                                        $status->fatal( 'backend-fail-synced', $path );
                                                        continue;
                                                }
@@ -234,6 +250,44 @@ class FileBackendMultiWrite extends FileBackend {
                return $status;
        }
 
+       /**
+        * Check that a set of files are consistent across all internal backends
+        * and re-synchronize those files againt the "multi master" if needed.
+        *
+        * @param $paths Array List of storage paths
+        * @return Status
+        */
+       public function resyncFiles( array $paths ) {
+               $status = Status::newGood();
+
+               $mBackend = $this->backends[$this->masterIndex];
+               foreach ( $paths as $path ) {
+                       $mPath  = $this->substPaths( $path, $mBackend );
+                       $mSha1  = $mBackend->getFileSha1Base36( array( 'src' => $mPath ) );
+                       $mExist = $mBackend->fileExists( array( 'src' => $mPath ) );
+                       // Check of all clone backends agree with the master...
+                       foreach ( $this->backends as $index => $cBackend ) {
+                               if ( $index === $this->masterIndex ) {
+                                       continue; // master
+                               }
+                               $cPath = $this->substPaths( $path, $cBackend );
+                               $cSha1 = $cBackend->getFileSha1Base36( array( 'src' => $cPath ) );
+                               if ( $mSha1 === $cSha1 ) {
+                                       // already synced; nothing to do
+                               } elseif ( $mSha1 ) { // file is in master
+                                       $fsFile = $mBackend->getLocalReference( array( 'src' => $mPath ) );
+                                       $status->merge( $cBackend->quickStore(
+                                               array( 'src' => $fsFile->getPath(), 'dst' => $cPath )
+                                       ) );
+                               } elseif ( $mExist === false ) { // file is not in master
+                                       $status->merge( $cBackend->quickDelete( array( 'src' => $cPath ) ) );
+                               }
+                       }
+               }
+
+               return $status;
+       }
+
        /**
         * Get a list of file storage paths to read or write for a list of operations
         *
@@ -330,10 +384,12 @@ class FileBackendMultiWrite extends FileBackend {
                $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
                $status->merge( $masterStatus );
                // Propagate the operations to the clone backends...
-               foreach ( $this->backends as $index => $backend ) {
-                       if ( $index !== $this->masterIndex ) { // not done already
-                               $realOps = $this->substOpBatchPaths( $ops, $backend );
-                               $status->merge( $backend->doQuickOperations( $realOps ) );
+               if ( !$this->noPushQuickOps ) {
+                       foreach ( $this->backends as $index => $backend ) {
+                               if ( $index !== $this->masterIndex ) { // not done already
+                                       $realOps = $this->substOpBatchPaths( $ops, $backend );
+                                       $status->merge( $backend->doQuickOperations( $realOps ) );
+                               }
                        }
                }
                // Make 'success', 'successCount', and 'failCount' fields reflect
@@ -345,15 +401,27 @@ class FileBackendMultiWrite extends FileBackend {
                return $status;
        }
 
+       /**
+        * @param $path string Storage path
+        * @return bool Path container should have dir changes pushed to all backends
+        */
+       protected function replicateContainerDirChanges( $path ) {
+               list( $b, $shortCont, $r ) = self::splitStoragePath( $path );
+               return !in_array( $shortCont, $this->noPushDirConts );
+       }
+
        /**
         * @see FileBackend::doPrepare()
         * @return Status
         */
        protected function doPrepare( array $params ) {
                $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doPrepare( $realParams ) );
+               $replicate = $this->replicateContainerDirChanges( $params['dir'] );
+               foreach ( $this->backends as $index => $backend ) {
+                       if ( $replicate || $index == $this->masterIndex ) {
+                               $realParams = $this->substOpPaths( $params, $backend );
+                               $status->merge( $backend->doPrepare( $realParams ) );
+                       }
                }
                return $status;
        }
@@ -365,9 +433,12 @@ class FileBackendMultiWrite extends FileBackend {
         */
        protected function doSecure( array $params ) {
                $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doSecure( $realParams ) );
+               $replicate = $this->replicateContainerDirChanges( $params['dir'] );
+               foreach ( $this->backends as $index => $backend ) {
+                       if ( $replicate || $index == $this->masterIndex ) {
+                               $realParams = $this->substOpPaths( $params, $backend );
+                               $status->merge( $backend->doSecure( $realParams ) );
+                       }
                }
                return $status;
        }
@@ -379,9 +450,12 @@ class FileBackendMultiWrite extends FileBackend {
         */
        protected function doPublish( array $params ) {
                $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doPublish( $realParams ) );
+               $replicate = $this->replicateContainerDirChanges( $params['dir'] );
+               foreach ( $this->backends as $index => $backend ) {
+                       if ( $replicate || $index == $this->masterIndex ) {
+                               $realParams = $this->substOpPaths( $params, $backend );
+                               $status->merge( $backend->doPublish( $realParams ) );
+                       }
                }
                return $status;
        }
@@ -393,9 +467,12 @@ class FileBackendMultiWrite extends FileBackend {
         */
        protected function doClean( array $params ) {
                $status = Status::newGood();
-               foreach ( $this->backends as $backend ) {
-                       $realParams = $this->substOpPaths( $params, $backend );
-                       $status->merge( $backend->doClean( $realParams ) );
+               $replicate = $this->replicateContainerDirChanges( $params['dir'] );
+               foreach ( $this->backends as $index => $backend ) {
+                       if ( $replicate || $index == $this->masterIndex ) {
+                               $realParams = $this->substOpPaths( $params, $backend );
+                               $status->merge( $backend->doClean( $realParams ) );
+                       }
                }
                return $status;
        }