*/
/**
+ * Base class for all file backends.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup FileBackend
* @author Aaron Schulz
protected $name; // string; unique backend name
protected $wikiId; // string; unique wiki name
protected $readOnly; // string; read-only explanation message
+ protected $parallelize; // string; when to do operations in parallel
+ protected $concurrency; // integer; how many operations can be done in parallel
+
/** @var LockManager */
protected $lockManager;
/** @var FileJournal */
* Journals simply log changes to files stored in the backend.
* 'readOnly' : Write operations are disallowed if this is a non-empty string.
* It should be an explanation for the backend being read-only.
+ * 'parallelize' : When to do file operations in parallel (when possible).
+ * Allowed values are "implicit", "explicit" and "off".
+ * 'concurrency' : How many file operations can be done in parallel.
*
* @param $config Array
+ * @throws MWException
*/
public function __construct( array $config ) {
$this->name = $config['name'];
$this->readOnly = isset( $config['readOnly'] )
? (string)$config['readOnly']
: '';
+ $this->parallelize = isset( $config['parallelize'] )
+ ? (string)$config['parallelize']
+ : 'off';
+ $this->concurrency = isset( $config['concurrency'] )
+ ? (int)$config['concurrency']
+ : 50;
}
/**
* This has no effect unless the 'force' flag is set.
* 'nonJournaled' : Don't log this operation batch in the file journal.
* This limits the ability of recovery scripts.
+ * 'parallelize' : Try to do operations in parallel when possible.
*
* Remarks on locking:
* File system paths given to operations should refer to files that are
unset( $opts['nonLocking'] );
unset( $opts['allowStale'] );
}
+ $opts['concurrency'] = 1; // off
+ if ( $this->parallelize === 'implicit' ) {
+ if ( !isset( $opts['parallelize'] ) || $opts['parallelize'] ) {
+ $opts['concurrency'] = $this->concurrency;
+ }
+ } elseif ( $this->parallelize === 'explicit' ) {
+ if ( !empty( $opts['parallelize'] ) ) {
+ $opts['concurrency'] = $this->concurrency;
+ }
+ }
return $this->doOperationsInternal( $ops, $opts );
}
* @return Status
*/
final public function create( array $params, array $opts = array() ) {
- $params['op'] = 'create';
- return $this->doOperation( $params, $opts );
+ return $this->doOperation( array( 'op' => 'create' ) + $params, $opts );
}
/**
* @return Status
*/
final public function store( array $params, array $opts = array() ) {
- $params['op'] = 'store';
- return $this->doOperation( $params, $opts );
+ return $this->doOperation( array( 'op' => 'store' ) + $params, $opts );
}
/**
* @return Status
*/
final public function copy( array $params, array $opts = array() ) {
- $params['op'] = 'copy';
- return $this->doOperation( $params, $opts );
+ return $this->doOperation( array( 'op' => 'copy' ) + $params, $opts );
}
/**
* @return Status
*/
final public function move( array $params, array $opts = array() ) {
- $params['op'] = 'move';
- return $this->doOperation( $params, $opts );
+ return $this->doOperation( array( 'op' => 'move' ) + $params, $opts );
}
/**
* @return Status
*/
final public function delete( array $params, array $opts = array() ) {
- $params['op'] = 'delete';
- return $this->doOperation( $params, $opts );
+ return $this->doOperation( array( 'op' => 'delete' ) + $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
* $params include:
* dir : storage directory
*
+ * @param $params array
* @return bool|null Returns null on failure
* @since 1.20
*/
* dir : storage directory
* topOnly : only return direct child dirs of the directory
*
+ * @param $params array
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
* $params include:
* dir : storage directory
*
+ * @param $params array
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
* dir : storage directory
* topOnly : only return direct child files of the directory (@since 1.20)
*
+ * @param $params array
* @return Traversable|Array|null Returns null on failure
*/
abstract public function getFileList( array $params );
* $params include:
* dir : storage directory
*
+ * @param $params array
* @return Traversable|Array|null Returns null on failure
* @since 1.20
*/
return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
}
+ /**
+ * Get an array of scoped locks needed for a batch of file operations.
+ *
+ * Normally, FileBackend::doOperations() handles locking, unless
+ * the 'nonLocking' param is passed in. This function is useful if you
+ * want the files to be locked for a broader scope than just when the
+ * files are changing. For example, if you need to update DB metadata,
+ * you may want to keep the files locked until finished.
+ *
+ * @see FileBackend::doOperations()
+ *
+ * @param $ops Array List of file operations to FileBackend::doOperations()
+ * @param $status Status Status to update on lock/unlock
+ * @return Array List of ScopedFileLocks or null values
+ * @since 1.20
+ */
+ abstract public function getScopedLocksForOps( array $ops, Status $status );
+
/**
* Get the root storage path of this backend.
* All container paths are "subdirectories" of this path.
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.
<?php
/**
+ * Proxy backend that mirrors writes to several internal backends.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup FileBackend
* @author Aaron Schulz
* 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." );
}
// Actually attempt the operation batch...
- $subStatus = FileOp::attemptBatch( $performOps, $opts, $this->fileJournal );
+ $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
$success = array();
$failCount = 0;
/**
* Same as substOpBatchPaths() but for a single operation
*
- * @param $op File operation array
+ * @param $ops array File operation array
* @param $backend FileBackendStore
* @return Array
*/
);
}
+ /**
+ * @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
/**
* @see FileBackend::doSecure()
+ * @param $params array
* @return Status
*/
protected function doSecure( array $params ) {
/**
* @see FileBackend::doClean()
+ * @param $params array
* @return Status
*/
protected function doClean( array $params ) {
/**
* @see FileBackend::concatenate()
+ * @param $params array
+ * @return Status
*/
public function concatenate( array $params ) {
// We are writing to an FS file, so we don't need to do this per-backend
/**
* @see FileBackend::fileExists()
+ * @param $params array
*/
public function fileExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileTimestamp()
+ * @param $params array
+ * @return bool|string
*/
public function getFileTimestamp( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileSize()
+ * @param $params array
+ * @return bool|int
*/
public function getFileSize( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileStat()
+ * @param $params array
+ * @return Array|bool|null
*/
public function getFileStat( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileContents()
+ * @param $params array
+ * @return bool|string
*/
public function getFileContents( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileSha1Base36()
+ * @param $params array
+ * @return bool|string
*/
public function getFileSha1Base36( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileProps()
+ * @param $params array
+ * @return Array
*/
public function getFileProps( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::streamFile()
+ * @param $params array
+ * @return \Status
*/
public function streamFile( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getLocalReference()
+ * @param $params array
+ * @return FSFile|null
*/
public function getLocalReference( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getLocalCopy()
+ * @param $params array
+ * @return null|TempFSFile
*/
public function getLocalCopy( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::directoryExists()
+ * @param $params array
+ * @return bool|null
*/
public function directoryExists( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getSubdirectoryList()
+ * @param $params array
+ * @return Array|null|Traversable
*/
public function getDirectoryList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
/**
* @see FileBackend::getFileList()
+ * @param $params array
+ * @return Array|null|\Traversable
*/
public function getFileList( array $params ) {
$realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
$backend->clearCache( $realPaths );
}
}
+
+ /**
+ * @see FileBackend::getScopedLocksForOps()
+ */
+ public function getScopedLocksForOps( array $ops, Status $status ) {
+ $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $ops );
+ // Get the paths to lock from the master backend
+ $paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
+ // Get the paths under the proxy backend's name
+ $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
+ $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
+ return array(
+ $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
+ $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
+ );
+ }
}
<?php
/**
+ * Base class for all backends using particular storage medium.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup FileBackend
* @author Aaron Schulz
* content : the raw file contents
* dst : destination storage path
* overwrite : overwrite any file that exists at the destination
+ * async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
*
* @param $params Array
* @return Status
* src : source path on disk
* dst : destination storage path
* overwrite : overwrite any file that exists at the destination
+ * async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
*
* @param $params Array
* @return Status
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
- $status = Status::newFatal( 'backend-fail-store', $params['dst'] );
+ $status = Status::newFatal( 'backend-fail-maxsize',
+ $params['dst'], $this->maxFileSizeInternal() );
} else {
$status = $this->doStoreInternal( $params );
$this->clearCache( array( $params['dst'] ) );
* src : source storage path
* dst : destination storage path
* overwrite : overwrite any file that exists at the destination
+ * async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
*
* @param $params Array
* @return Status
* $params include:
* src : source storage path
* ignoreMissingSource : do nothing if the source file does not exist
+ * async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
*
* @param $params Array
* @return Status
* src : source storage path
* dst : destination storage path
* overwrite : overwrite any file that exists at the destination
+ * async : Status will be returned immediately if supported.
+ * If the status is OK, then its value field will be
+ * set to a FileBackendStoreOpHandle object.
*
* @param $params Array
* @return Status
* @return Status
*/
protected function doMoveInternal( array $params ) {
+ unset( $params['async'] ); // two steps, won't work here :)
// Copy source to dest
$status = $this->copyInternal( $params );
if ( $status->isOK() ) {
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
$this->trimCache(); // limit memory
$this->cache[$path]['stat'] = $stat;
$this->setFileCache( $path, $stat ); // update persistent cache
+ } else {
+ wfDebug( __METHOD__ . ": File $path does not exist.\n" );
}
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
*/
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 $paths;
}
+ /**
+ * @see FileBackend::getScopedLocksForOps()
+ * @return Array
+ */
+ public function getScopedLocksForOps( array $ops, Status $status ) {
+ $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
+ return array(
+ $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
+ $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
+ );
+ }
+
/**
* @see FileBackend::doOperationsInternal()
* @return Status
$this->primeContainerCache( $performOps );
// Actually attempt the operation batch...
- $subStatus = FileOp::attemptBatch( $performOps, $opts, $this->fileJournal );
+ $subStatus = FileOpBatch::attempt( $performOps, $opts, $this->fileJournal );
// Merge errors into status fields
$status->merge( $subStatus );
return $status;
}
+ /**
+ * @see FileBackend::doQuickOperationsInternal()
+ * @return Status
+ * @throws MWException
+ */
+ final protected function doQuickOperationsInternal( array $ops ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
+ $status = Status::newGood();
+
+ $supportedOps = array( 'create', 'store', 'copy', 'move', 'delete', 'null' );
+ $async = ( $this->parallelize === 'implicit' );
+ $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 ) {
+ if ( !in_array( $params['op'], $supportedOps ) ) {
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ throw new MWException( "Operation '{$params['op']}' is not supported." );
+ }
+ $method = $params['op'] . 'Internal'; // e.g. "storeInternal"
+ $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;
+ }
+ }
+
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * Execute a list of FileBackendStoreOpHandle handles in parallel.
+ * The resulting Status object fields will correspond
+ * to the order in which the handles where given.
+ *
+ * @param $handles Array List of FileBackendStoreOpHandle objects
+ * @return Array Map of Status objects
+ * @throws MWException
+ */
+ final public function executeOpHandlesInternal( array $fileOpHandles ) {
+ wfProfileIn( __METHOD__ );
+ wfProfileIn( __METHOD__ . '-' . $this->name );
+ foreach ( $fileOpHandles as $fileOpHandle ) {
+ if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
+ throw new MWException( "Given a non-FileBackendStoreOpHandle object." );
+ } elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
+ throw new MWException( "Given a FileBackendStoreOpHandle for the wrong backend." );
+ }
+ }
+ $res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
+ foreach ( $fileOpHandles as $fileOpHandle ) {
+ $fileOpHandle->closeResources();
+ }
+ wfProfileOut( __METHOD__ . '-' . $this->name );
+ wfProfileOut( __METHOD__ );
+ return $res;
+ }
+
+ /**
+ * @see FileBackendStore::executeOpHandlesInternal()
+ * @return Array List of corresponding Status objects
+ */
+ protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
+ foreach ( $fileOpHandles as $fileOpHandle ) { // OK if empty
+ throw new MWException( "This backend supports no asynchronous operations." );
+ }
+ return array();
+ }
+
/**
* @see FileBackend::clearCache()
*/
* Any empty suffix means the container is not sharded.
*
* @param $container string Container name
- * @param $relStoragePath string Storage path relative to the container
+ * @param $relPath string Storage path relative to the container
* @return string|null Returns null if shard could not be determined
*/
final protected function getContainerShard( $container, $relPath ) {
/**
* 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
*/
final protected function setContainerCache( $container, $val ) {
$this->memCache->set( $this->containerCacheKey( $container ), $val, 14*86400 );
/**
* Delete the cached info for a container
*
- * @param $container Resolved container name
- * @return void
+ * @param $container string Resolved container name
*/
final protected function deleteContainerCache( $container ) {
- for ( $attempts=1; $attempts <= 3; $attempts++ ) {
- if ( $this->memCache->delete( $this->containerCacheKey( $container ) ) ) {
- return; // done!
- }
+ if ( !$this->memCache->delete( $this->containerCacheKey( $container ) ) ) {
+ trigger_error( "Unable to delete stat cache for container $container." );
}
- trigger_error( "Unable to delete stat cache for container $container." );
}
/**
final protected function primeContainerCache( array $items ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
+
$paths = array(); // list of storage paths
$contNames = array(); // (cache key => resolved container name)
// Get all the paths/containers from the items...
$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;
}
// Populate the container process cache for the backend...
$this->doPrimeContainerCache( array_filter( $contInfo, 'is_array' ) );
+
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
}
/**
* 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
*/
final protected function setFileCache( $path, $val ) {
$this->memCache->set( $this->fileCacheKey( $path ), $val, 7*86400 );
/**
* Delete the cached stat info for a file path
*
- * @param $path Storage path
- * @return void
+ * @param $path string Storage path
*/
final protected function deleteFileCache( $path ) {
- for ( $attempts=1; $attempts <= 3; $attempts++ ) {
- if ( $this->memCache->delete( $this->fileCacheKey( $path ) ) ) {
- return; // done!
- }
+ if ( !$this->memCache->delete( $this->fileCacheKey( $path ) ) ) {
+ trigger_error( "Unable to delete stat cache for file $path." );
}
- trigger_error( "Unable to delete stat cache for file $path." );
}
/**
final protected function primeFileCache( array $items ) {
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-' . $this->name );
+
$paths = array(); // list of storage paths
$pathNames = array(); // (cache key => storage path)
// Get all the paths/containers from the items...
}
}
// 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
$this->cache[$pathNames[$cacheKey]]['stat'] = $val;
}
}
+
wfProfileOut( __METHOD__ . '-' . $this->name );
wfProfileOut( __METHOD__ );
}
}
+/**
+ * FileBackendStore helper class for performing asynchronous file operations.
+ *
+ * For example, calling FileBackendStore::createInternal() with the "async"
+ * param flag may result in a Status that contains this object as a value.
+ * This class is largely backend-specific and is mostly just "magic" to be
+ * passed to FileBackendStore::executeOpHandlesInternal().
+ */
+abstract class FileBackendStoreOpHandle {
+ /** @var Array */
+ public $params = array(); // params to caller functions
+ /** @var FileBackendStore */
+ public $backend;
+ /** @var Array */
+ public $resourcesToClose = array();
+
+ public $call; // string; name that identifies the function called
+
+ /**
+ * Close all open file handles
+ *
+ * @return void
+ */
+ public function closeResources() {
+ array_map( 'fclose', $this->resourcesToClose );
+ }
+}
+
/**
* FileBackendStore helper function to handle listings that span container shards.
* Do not use this class from places outside of FileBackendStore.
* Iterator for listing directories
*/
class FileBackendStoreShardDirIterator extends FileBackendStoreShardListIterator {
+ /**
+ * @param string $container
+ * @param string $dir
+ * @param array $params
+ * @return Array|null|Traversable
+ */
protected function listFromShard( $container, $dir, array $params ) {
return $this->backend->getDirectoryListInternal( $container, $dir, $params );
}
* Iterator for listing regular files
*/
class FileBackendStoreShardFileIterator extends FileBackendStoreShardListIterator {
+ /**
+ * @param string $container
+ * @param string $dir
+ * @param array $params
+ * @return Array|null|Traversable
+ */
protected function listFromShard( $container, $dir, array $params ) {
return $this->backend->getFileListInternal( $container, $dir, $params );
}