Merge "[FileBackend] Added doQuickOperations() function for things like purging thumb...
authorTim Starling <tstarling@wikimedia.org>
Thu, 17 May 2012 07:42:07 +0000 (07:42 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 17 May 2012 07:42:07 +0000 (07:42 +0000)
1  2 
includes/filerepo/backend/FileBackend.php
includes/filerepo/backend/FileBackendMultiWrite.php
includes/filerepo/backend/FileBackendStore.php

@@@ -350,6 -350,77 +350,77 @@@ abstract class FileBackend 
                return $this->doOperation( $params, $opts );
        }
  
+       /**
+        * Perform a set of independent file operations on some files.
+        *
+        * This does no locking, nor journaling, and possibly no stat calls.
+        * Any destination files that already exist will be overwritten.
+        * This should *only* be used on non-original files, like cache files.
+        *
+        * Supported operations and their parameters:
+        * a) Create a new file in storage with the contents of a string
+        *     array(
+        *         'op'                  => 'create',
+        *         'dst'                 => <storage path>,
+        *         'content'             => <string of new file contents>
+        *     )
+        * b) Copy a file system file into storage
+        *     array(
+        *         'op'                  => 'store',
+        *         'src'                 => <file system path>,
+        *         'dst'                 => <storage path>
+        *     )
+        * c) Copy a file within storage
+        *     array(
+        *         'op'                  => 'copy',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>
+        *     )
+        * d) Move a file within storage
+        *     array(
+        *         'op'                  => 'move',
+        *         'src'                 => <storage path>,
+        *         'dst'                 => <storage path>
+        *     )
+        * e) Delete a file within storage
+        *     array(
+        *         'op'                  => 'delete',
+        *         'src'                 => <storage path>,
+        *         'ignoreMissingSource' => <boolean>
+        *     )
+        * f) Do nothing (no-op)
+        *     array(
+        *         'op'                  => 'null',
+        *     )
+        *
+        * Boolean flags for operations (operation-specific):
+        * 'ignoreMissingSource' : The operation will simply succeed and do
+        *                         nothing if the source file does not exist.
+        *
+        * Return value:
+        * This returns a Status, which contains all warnings and fatals that occured
+        * during the operation. The 'failCount', 'successCount', and 'success' members
+        * will reflect each operation attempted for the given files. The status will be
+        * considered "OK" as long as no fatal errors occured.
+        *
+        * @param $ops Array Set of operations to execute
+        * @return Status
+        */
+       final public function doQuickOperations( array $ops ) {
+               if ( $this->isReadOnly() ) {
+                       return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+               }
+               foreach ( $ops as &$op ) {
+                       $op['overwrite'] = true; // avoids RTTs in key/value stores
+               }
+               return $this->doQuickOperationsInternal( $ops );
+       }
+       /**
+        * @see FileBackend::doQuickOperations()
+        */
+       abstract protected function doQuickOperationsInternal( array $ops );
        /**
         * Concatenate a list of storage files into a single file system file.
         * The target path should refer to a file that is already locked or
                return "mwstore://{$this->name}";
        }
  
 +      /**
 +       * Get the file journal object for this backend
 +       *
 +       * @return FileJournal
 +       */
 +      final public function getJournal() {
 +              return $this->fileJournal;
 +      }
 +
        /**
         * Check if a given path is a "mwstore://" path.
         * This does not do any further validation or any existence checks.
@@@ -57,9 -57,6 +57,9 @@@ class FileBackendMultiWrite extends Fil
         *                     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 self::CHECK_SIZE and self::CHECK_TIME.
         *                     The checks are done before allowing any file operations.
                // Construct backends here rather than via registration
                // to keep these backends hidden from outside the proxy.
                foreach ( $config['backends'] as $index => $config ) {
 +                      if ( isset( $config['template'] ) ) {
 +                              // Config is just a modified version of a registered backend's.
 +                              // This should only be used when that config is used only be this backend.
 +                              $config = $config + FileBackendGroup::singleton()->config( $config['template'] );
 +                      }
                        $name = $config['name'];
                        if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
                                throw new MWException( "Two or more backends defined with the name $name." );
                );
        }
  
+       /**
+        * @see FileBackend::doQuickOperationsInternal()
+        * @return Status
+        */
+       public function doQuickOperationsInternal( array $ops ) {
+               // Do the operations on the master backend; setting Status fields...
+               $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
+               $status = $this->backends[$this->masterIndex]->doQuickOperations( $realOps );
+               // 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 ) );
+                       }
+               }
+               return $status;
+       }
        /**
         * @see FileBackend::doPrepare()
         * @return Status
@@@ -257,6 -257,17 +257,17 @@@ abstract class FileBackendStore extend
                return $status;
        }
  
+       /**
+        * No-op file operation that does nothing.
+        * Do not call this function from places outside FileBackend and FileOp.
+        *
+        * @param $params Array
+        * @return Status
+        */
+       final public function nullInternal( array $params ) {
+               return Status::newGood();
+       }
        /**
         * @see FileBackend::concatenate()
         * @return Status
         */
        abstract public function getFileListInternal( $container, $dir, array $params );
  
-       /**
-        * Get the list of supported operations and their corresponding FileOp classes.
-        *
-        * @return Array
-        */
-       protected function supportedOperations() {
-               return array(
-                       'store'       => 'StoreFileOp',
-                       'copy'        => 'CopyFileOp',
-                       'move'        => 'MoveFileOp',
-                       'delete'      => 'DeleteFileOp',
-                       'create'      => 'CreateFileOp',
-                       'null'        => 'NullFileOp'
-               );
-       }
        /**
         * Return a list of FileOp objects from a list of operations.
         * Do not call this function from places outside FileBackend.
         * @throws MWException
         */
        final public function getOperationsInternal( array $ops ) {
-               $supportedOps = $this->supportedOperations();
+               $supportedOps = array(
+                       'store'       => 'StoreFileOp',
+                       'copy'        => 'CopyFileOp',
+                       'move'        => 'MoveFileOp',
+                       'delete'      => 'DeleteFileOp',
+                       'create'      => 'CreateFileOp',
+                       'null'        => 'NullFileOp'
+               );
  
                $performOps = array(); // array of FileOp objects
                // Build up ordered array of FileOps...
                return $status;
        }
  
+       /**
+        * @see FileBackend::doQuickOperationsInternal()
+        * @return Status
+        * @throws MWException
+        */
+       final protected function doQuickOperationsInternal( array $ops ) {
+               $status = Status::newGood();
+               $async = $this->parallelize;
+               $maxConcurrency = $this->concurrency; // throttle
+               $statuses = array(); // array of (index => Status)
+               $fileOpHandles = array(); // list of (index => handle) arrays
+               $curFileOpHandles = array(); // current handle batch
+               // Perform the sync-only ops and build up op handles for the async ops...
+               foreach ( $ops as $index => $params ) {
+                       $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
+                       if ( !MWInit::methodExists( __CLASS__, $method ) ) {
+                               throw new MWException( "Operation '{$params['op']}' is not supported." );
+                       }
+                       $subStatus = $this->$method( array( 'async' => $async ) + $params );
+                       if ( $subStatus->value instanceof FileBackendStoreOpHandle ) { // async
+                               if ( count( $curFileOpHandles ) >= $maxConcurrency ) {
+                                       $fileOpHandles[] = $curFileOpHandles; // push this batch
+                                       $curFileOpHandles = array();
+                               }
+                               $curFileOpHandles[$index] = $subStatus->value; // keep index
+                       } else { // error or completed
+                               $statuses[$index] = $subStatus; // keep index
+                       }
+               }
+               if ( count( $curFileOpHandles ) ) {
+                       $fileOpHandles[] = $curFileOpHandles; // last batch
+               }
+               // Do all the async ops that can be done concurrently...
+               foreach ( $fileOpHandles as $fileHandleBatch ) {
+                       $statuses = $statuses + $this->executeOpHandlesInternal( $fileHandleBatch );
+               }
+               // Marshall and merge all the responses...
+               foreach ( $statuses as $index => $subStatus ) {
+                       $status->merge( $subStatus );
+                       if ( $subStatus->isOK() ) {
+                               $status->success[$index] = true;
+                               ++$status->successCount;
+                       } else {
+                               $status->success[$index] = false;
+                               ++$status->failCount;
+                       }
+               }
+               return $status;
+       }
        /**
         * Execute a list of FileBackendStoreOpHandle handles in parallel.
         * The resulting Status object fields will correspond
         *
         * @param $handles Array List of FileBackendStoreOpHandle objects
         * @return Array Map of Status objects
+        * @throws MWException
         */
        final public function executeOpHandlesInternal( array $fileOpHandles ) {
                wfProfileIn( __METHOD__ );
        /**
         * @see FileBackendStore::executeOpHandlesInternal()
         * @return Array List of corresponding Status objects
+        * @throws MWException
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
                foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
        /**
         * Get the cache key for a container
         *
-        * @param $container Resolved container name
+        * @param $container string Resolved container name
         * @return string
         */
        private function containerCacheKey( $container ) {
        /**
         * Set the cached info for a container
         *
-        * @param $container Resolved container name
+        * @param $container string Resolved container name
         * @param $val mixed Information to cache
         * @return void
         */
        /**
         * Delete the cached info for a container
         *
-        * @param $container Resolved container name
+        * @param $containers string Resolved container name
         * @return void
         */
        final protected function deleteContainerCache( $container ) {
  
                $contInfo = array(); // (resolved container name => cache value)
                // Get all cache entries for these container cache keys...
 -              $values = $this->memCache->getBatch( array_keys( $contNames ) );
 +              $values = $this->memCache->getMulti( array_keys( $contNames ) );
                foreach ( $values as $cacheKey => $val ) {
                        $contInfo[$contNames[$cacheKey]] = $val;
                }
        /**
         * Get the cache key for a file path
         *
-        * @param $path Storage path
+        * @param $path string Storage path
         * @return string
         */
        private function fileCacheKey( $path ) {
        /**
         * Set the cached stat info for a file path
         *
-        * @param $path Storage path
+        * @param $path string Storage path
         * @param $val mixed Information to cache
         * @return void
         */
        /**
         * Delete the cached stat info for a file path
         *
-        * @param $path Storage path
+        * @param $path string Storage path
         * @return void
         */
        final protected function deleteFileCache( $path ) {
                        }
                }
                // Get all cache entries for these container cache keys...
 -              $values = $this->memCache->getBatch( array_keys( $pathNames ) );
 +              $values = $this->memCache->getMulti( array_keys( $pathNames ) );
                foreach ( $values as $cacheKey => $val ) {
                        if ( is_array( $val ) ) {
                                $this->trimCache(); // limit memory