diff options
Diffstat (limited to 'includes/filebackend')
-rw-r--r-- | includes/filebackend/FSFile.php | 9 | ||||
-rw-r--r-- | includes/filebackend/FileBackend.php | 15 | ||||
-rw-r--r-- | includes/filebackend/FileBackendGroup.php | 2 | ||||
-rw-r--r-- | includes/filebackend/FileBackendMultiWrite.php | 176 | ||||
-rw-r--r-- | includes/filebackend/FileBackendStore.php | 80 | ||||
-rw-r--r-- | includes/filebackend/FileOp.php | 4 | ||||
-rw-r--r-- | includes/filebackend/MemoryFileBackend.php | 8 | ||||
-rw-r--r-- | includes/filebackend/SwiftFileBackend.php | 151 | ||||
-rw-r--r-- | includes/filebackend/TempFSFile.php | 15 | ||||
-rw-r--r-- | includes/filebackend/lockmanager/DBLockManager.php | 2 | ||||
-rw-r--r-- | includes/filebackend/lockmanager/FSLockManager.php | 4 | ||||
-rw-r--r-- | includes/filebackend/lockmanager/LockManager.php | 1 |
12 files changed, 267 insertions, 200 deletions
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php index 6ee9b2e0..213bb87d 100644 --- a/includes/filebackend/FSFile.php +++ b/includes/filebackend/FSFile.php @@ -75,9 +75,9 @@ class FSFile { * @return string|bool TS_MW timestamp or false on failure */ public function getTimestamp() { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $timestamp = filemtime( $this->path ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $timestamp !== false ) { $timestamp = wfTimestamp( TS_MW, $timestamp ); } @@ -200,13 +200,12 @@ class FSFile { public function getSha1Base36( $recache = false ) { if ( $this->sha1Base36 !== null && !$recache ) { - return $this->sha1Base36; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $this->sha1Base36 = sha1_file( $this->path ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $this->sha1Base36 !== false ) { $this->sha1Base36 = wfBaseConvert( $this->sha1Base36, 16, 36, 31 ); diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php index b87e26d3..cd82ab10 100644 --- a/includes/filebackend/FileBackend.php +++ b/includes/filebackend/FileBackend.php @@ -380,12 +380,15 @@ abstract class FileBackend { $op['headers']['Content-Disposition'] = $op['disposition']; } } + /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doOperationsInternal( $ops, $opts ); } /** * @see FileBackend::doOperations() + * @param array $ops + * @param array $opts */ abstract protected function doOperationsInternal( array $ops, array $opts ); @@ -612,12 +615,14 @@ abstract class FileBackend { $op['headers']['Content-Disposition'] = $op['disposition']; } } + /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doQuickOperationsInternal( $ops ); } /** * @see FileBackend::doQuickOperations() + * @param array $ops * @since 1.20 */ abstract protected function doQuickOperationsInternal( array $ops ); @@ -756,12 +761,14 @@ abstract class FileBackend { if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); } + /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doPrepare( $params ); } /** * @see FileBackend::prepare() + * @param array $params */ abstract protected function doPrepare( array $params ); @@ -785,12 +792,14 @@ abstract class FileBackend { if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); } + /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doSecure( $params ); } /** * @see FileBackend::secure() + * @param array $params */ abstract protected function doSecure( array $params ); @@ -816,12 +825,14 @@ abstract class FileBackend { if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); } + /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doPublish( $params ); } /** * @see FileBackend::publish() + * @param array $params */ abstract protected function doPublish( array $params ); @@ -840,12 +851,14 @@ abstract class FileBackend { if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) { return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly ); } + /** @noinspection PhpUnusedLocalVariableInspection */ $scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts return $this->doClean( $params ); } /** * @see FileBackend::clean() + * @param array $params */ abstract protected function doClean( array $params ); @@ -1289,7 +1302,7 @@ abstract class FileBackend { * * @param array $ops List of file operations to FileBackend::doOperations() * @param Status $status Status to update on lock/unlock - * @return array List of ScopedFileLocks or null values + * @return ScopedLock|null * @since 1.20 */ abstract public function getScopedLocksForOps( array $ops, Status $status ); diff --git a/includes/filebackend/FileBackendGroup.php b/includes/filebackend/FileBackendGroup.php index 1b88db7e..9bb15825 100644 --- a/includes/filebackend/FileBackendGroup.php +++ b/includes/filebackend/FileBackendGroup.php @@ -160,6 +160,8 @@ class FileBackendGroup { $config['fileJournal'] = isset( $config['fileJournal'] ) ? FileJournal::factory( $config['fileJournal'], $name ) : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $name ); + $config['wanCache'] = ObjectCache::getMainWANInstance(); + $this->backends[$name]['instance'] = new $class( $config ); } diff --git a/includes/filebackend/FileBackendMultiWrite.php b/includes/filebackend/FileBackendMultiWrite.php index f2d13eeb..1603d44f 100644 --- a/includes/filebackend/FileBackendMultiWrite.php +++ b/includes/filebackend/FileBackendMultiWrite.php @@ -33,20 +33,22 @@ * Only use this class when transitioning from one storage system to another. * * Read operations are only done on the 'master' backend for consistency. - * Write operations are performed on all backends, in the order defined. - * If an operation fails on one backend it will be rolled back from the others. + * Write operations are performed on all backends, starting with the master. + * This makes a best-effort to have transactional semantics, but since requests + * may sometimes fail, the use of "autoResync" or background scripts to fix + * inconsistencies is important. * * @ingroup FileBackend * @since 1.19 */ class FileBackendMultiWrite extends FileBackend { - /** @var array Prioritized list of FileBackendStore objects. - * array of (backend index => backends) - */ + /** @var FileBackendStore[] Prioritized list of FileBackendStore objects */ protected $backends = array(); /** @var int Index of master backend */ protected $masterIndex = -1; + /** @var int Index of read affinity backend */ + protected $readIndex = -1; /** @var int Bitfield */ protected $syncChecks = 0; @@ -54,12 +56,6 @@ class FileBackendMultiWrite extends FileBackend { /** @var string|bool */ protected $autoResync = false; - /** @var array */ - protected $noPushDirConts = array(); - - /** @var bool */ - protected $noPushQuickOps = false; - /* Possible internal backend consistency checks */ const CHECK_SIZE = 1; const CHECK_TIME = 2; @@ -75,6 +71,7 @@ class FileBackendMultiWrite extends FileBackend { * FileBackendStore class, but with these additional settings: * - class : The name of the backend class * - isMultiMaster : This must be set for one backend. + * - readAffinity : Use this for reads without 'latest' set. * - template: : If given a backend name, this will use * the config of that backend as a template. * Values specified here take precedence. @@ -88,8 +85,6 @@ class FileBackendMultiWrite extends FileBackend { * Use "conservative" to limit resyncing to copying newer master * backend files over older (or non-existing) clone backend files. * Cases that cannot be handled will result in operation abortion. - * - noPushQuickOps : (hack) Only apply doQuickOperations() to the master backend. - * - noPushDirConts : (hack) Only apply directory functions to the master backend. * * @param array $config * @throws FileBackendError @@ -102,12 +97,6 @@ class FileBackendMultiWrite extends FileBackend { $this->autoResync = isset( $config['autoResync'] ) ? $config['autoResync'] : false; - $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(); @@ -134,6 +123,9 @@ class FileBackendMultiWrite extends FileBackend { $this->masterIndex = $index; // this is the "master" $config['fileJournal'] = $this->fileJournal; // log under proxy backend } + if ( !empty( $config['readAffinity'] ) ) { + $this->readIndex = $index; // prefer this for reads + } // Create sub-backend object if ( !isset( $config['class'] ) ) { throw new FileBackendError( 'No class given for a backend config.' ); @@ -144,6 +136,9 @@ class FileBackendMultiWrite extends FileBackend { if ( $this->masterIndex < 0 ) { // need backends and must have a master throw new FileBackendError( 'No master backend defined.' ); } + if ( $this->readIndex < 0 ) { + $this->readIndex = $this->masterIndex; // default + } } final protected function doOperationsInternal( array $ops, array $opts ) { @@ -154,6 +149,7 @@ class FileBackendMultiWrite extends FileBackend { // Try to lock those files for the scope of this function... if ( empty( $opts['nonLocking'] ) ) { // Try to lock those files for the scope of this function... + /** @noinspection PhpUnusedLocalVariableInspection */ $scopeLock = $this->getScopedLocksForOps( $ops, $status ); if ( !$status->isOK() ) { return $status; // abort @@ -328,8 +324,8 @@ class FileBackendMultiWrite extends FileBackend { $cStat = $cBackend->getFileStat( array( 'src' => $cPath, 'latest' => true ) ); if ( $cStat === null || ( $cSha1 !== false && !$cStat ) ) { // sanity $status->fatal( 'backend-fail-internal', $cBackend->getName() ); - wfDebugLog( 'FileOperation', __METHOD__ - . ': File is not available on the clone backend' ); + wfDebugLog( 'FileOperation', __METHOD__ . + ': File is not available on the clone backend' ); continue; // file is not available on the clone backend... } if ( $mSha1 === $cSha1 ) { @@ -433,7 +429,7 @@ class FileBackendMultiWrite extends FileBackend { */ protected function substPaths( $paths, FileBackendStore $backend ) { return preg_replace( - '!^mwstore://' . preg_quote( $this->name ) . '/!', + '!^mwstore://' . preg_quote( $this->name, '!' ) . '/!', StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ), $paths // string or array ); @@ -460,12 +456,10 @@ class FileBackendMultiWrite extends FileBackend { $masterStatus = $this->backends[$this->masterIndex]->doQuickOperations( $realOps ); $status->merge( $masterStatus ); // Propagate the operations to the clone backends... - 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 ) ); - } + 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 @@ -478,24 +472,11 @@ class FileBackendMultiWrite extends FileBackend { return $status; } - /** - * @param string $path Storage path - * @return bool Path container should have dir changes pushed to all backends - */ - protected function replicateContainerDirChanges( $path ) { - list( , $shortCont, ) = self::splitStoragePath( $path ); - - return !in_array( $shortCont, $this->noPushDirConts ); - } - protected function doPrepare( array $params ) { $status = Status::newGood(); - $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 ) ); - } + $realParams = $this->substOpPaths( $params, $backend ); + $status->merge( $backend->doPrepare( $realParams ) ); } return $status; @@ -503,12 +484,9 @@ class FileBackendMultiWrite extends FileBackend { protected function doSecure( array $params ) { $status = Status::newGood(); - $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 ) ); - } + $realParams = $this->substOpPaths( $params, $backend ); + $status->merge( $backend->doSecure( $realParams ) ); } return $status; @@ -516,12 +494,9 @@ class FileBackendMultiWrite extends FileBackend { protected function doPublish( array $params ) { $status = Status::newGood(); - $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 ) ); - } + $realParams = $this->substOpPaths( $params, $backend ); + $status->merge( $backend->doPublish( $realParams ) ); } return $status; @@ -529,12 +504,9 @@ class FileBackendMultiWrite extends FileBackend { protected function doClean( array $params ) { $status = Status::newGood(); - $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 ) ); - } + $realParams = $this->substOpPaths( $params, $backend ); + $status->merge( $backend->doClean( $realParams ) ); } return $status; @@ -542,44 +514,52 @@ class FileBackendMultiWrite extends FileBackend { public function concatenate( array $params ) { // We are writing to an FS file, so we don't need to do this per-backend - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->concatenate( $realParams ); + return $this->backends[$index]->concatenate( $realParams ); } public function fileExists( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->fileExists( $realParams ); + return $this->backends[$index]->fileExists( $realParams ); } public function getFileTimestamp( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams ); + return $this->backends[$index]->getFileTimestamp( $realParams ); } public function getFileSize( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileSize( $realParams ); + return $this->backends[$index]->getFileSize( $realParams ); } public function getFileStat( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileStat( $realParams ); + return $this->backends[$index]->getFileStat( $realParams ); } public function getFileXAttributes( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileXAttributes( $realParams ); + return $this->backends[$index]->getFileXAttributes( $realParams ); } public function getFileContentsMulti( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); - $contentsM = $this->backends[$this->masterIndex]->getFileContentsMulti( $realParams ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); + + $contentsM = $this->backends[$index]->getFileContentsMulti( $realParams ); $contents = array(); // (path => FSFile) mapping using the proxy backend's name foreach ( $contentsM as $path => $data ) { @@ -590,26 +570,31 @@ class FileBackendMultiWrite extends FileBackend { } public function getFileSha1Base36( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams ); + return $this->backends[$index]->getFileSha1Base36( $realParams ); } public function getFileProps( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileProps( $realParams ); + return $this->backends[$index]->getFileProps( $realParams ); } public function streamFile( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->streamFile( $realParams ); + return $this->backends[$index]->streamFile( $realParams ); } public function getLocalReferenceMulti( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); - $fsFilesM = $this->backends[$this->masterIndex]->getLocalReferenceMulti( $realParams ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); + + $fsFilesM = $this->backends[$index]->getLocalReferenceMulti( $realParams ); $fsFiles = array(); // (path => FSFile) mapping using the proxy backend's name foreach ( $fsFilesM as $path => $fsFile ) { @@ -620,8 +605,10 @@ class FileBackendMultiWrite extends FileBackend { } public function getLocalCopyMulti( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); - $tempFilesM = $this->backends[$this->masterIndex]->getLocalCopyMulti( $realParams ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); + + $tempFilesM = $this->backends[$index]->getLocalCopyMulti( $realParams ); $tempFiles = array(); // (path => TempFSFile) mapping using the proxy backend's name foreach ( $tempFilesM as $path => $tempFile ) { @@ -632,9 +619,10 @@ class FileBackendMultiWrite extends FileBackend { } public function getFileHttpUrl( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); - return $this->backends[$this->masterIndex]->getFileHttpUrl( $realParams ); + return $this->backends[$index]->getFileHttpUrl( $realParams ); } public function directoryExists( array $params ) { @@ -667,13 +655,15 @@ class FileBackendMultiWrite extends FileBackend { } public function preloadCache( array $paths ) { - $realPaths = $this->substPaths( $paths, $this->backends[$this->masterIndex] ); - $this->backends[$this->masterIndex]->preloadCache( $realPaths ); + $realPaths = $this->substPaths( $paths, $this->backends[$this->readIndex] ); + $this->backends[$this->readIndex]->preloadCache( $realPaths ); } public function preloadFileStat( array $params ) { - $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); - return $this->backends[$this->masterIndex]->preloadFileStat( $realParams ); + $index = $this->getReadIndexFromParams( $params ); + $realParams = $this->substOpPaths( $params, $this->backends[$index] ); + + return $this->backends[$index]->preloadFileStat( $realParams ); } public function getScopedLocksForOps( array $ops, Status $status ) { @@ -688,6 +678,14 @@ class FileBackendMultiWrite extends FileBackend { ); // Actually acquire the locks - return array( $this->getScopedFileLocks( $pbPaths, 'mixed', $status ) ); + return $this->getScopedFileLocks( $pbPaths, 'mixed', $status ); + } + + /** + * @param array $params + * @return int The master or read affinity backend index, based on $params['latest'] + */ + protected function getReadIndexFromParams( array $params ) { + return !empty( $params['latest'] ) ? $this->masterIndex : $this->readIndex; } } diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php index 25e87d43..94339643 100644 --- a/includes/filebackend/FileBackendStore.php +++ b/includes/filebackend/FileBackendStore.php @@ -36,7 +36,7 @@ * @since 1.19 */ abstract class FileBackendStore extends FileBackend { - /** @var BagOStuff */ + /** @var WANObjectCache */ protected $memCache; /** @var ProcessCacheLRU Map of paths to small (RAM/disk) cache items */ protected $cheapCache; @@ -58,6 +58,7 @@ abstract class FileBackendStore extends FileBackend { /** * @see FileBackend::__construct() * Additional $config params include: + * - wanCache : WANOBjectCache object to use for persistent caching. * - mimeCallback : Callback that takes (storage path, content, file system path) and * returns the MIME type of the file or 'unknown/unknown'. The file * system path parameter should be used if the content one is null. @@ -72,7 +73,7 @@ abstract class FileBackendStore extends FileBackend { // @todo handle the case of extension-less files using the contents return StreamFile::contentTypeFromPath( $storagePath ) ?: 'unknown/unknown'; }; - $this->memCache = new EmptyBagOStuff(); // disabled by default + $this->memCache = WANObjectCache::newEmpty(); // disabled by default $this->cheapCache = new ProcessCacheLRU( self::CACHE_CHEAP_SIZE ); $this->expensiveCache = new ProcessCacheLRU( self::CACHE_EXPENSIVE_SIZE ); } @@ -376,9 +377,9 @@ abstract class FileBackendStore extends FileBackend { unset( $params['latest'] ); // sanity // Check that the specified temp file is valid... - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $ok = ( is_file( $tmpPath ) && filesize( $tmpPath ) == 0 ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$ok ) { // not present or not empty $status->fatal( 'backend-fail-opentemp', $tmpPath ); @@ -693,9 +694,9 @@ abstract class FileBackendStore extends FileBackend { protected function doGetFileContentsMulti( array $params ) { $contents = array(); foreach ( $this->doGetLocalReferenceMulti( $params ) as $path => $fsFile ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $contents[$path] = $fsFile ? file_get_contents( $fsFile->getPath() ) : false; - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } return $contents; @@ -1057,7 +1058,7 @@ abstract class FileBackendStore extends FileBackend { public function getScopedLocksForOps( array $ops, Status $status ) { $paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) ); - return array( $this->getScopedFileLocks( $paths, 'mixed', $status ) ); + return $this->getScopedFileLocks( $paths, 'mixed', $status ); } final protected function doOperationsInternal( array $ops, array $opts ) { @@ -1075,6 +1076,7 @@ abstract class FileBackendStore extends FileBackend { // Build up a list of files to lock... $paths = $this->getPathsToLockForOpsInternal( $performOps ); // Try to lock those files for the scope of this function... + $scopeLock = $this->getScopedFileLocks( $paths, 'mixed', $status ); if ( !$status->isOK() ) { return $status; // abort @@ -1363,19 +1365,38 @@ abstract class FileBackendStore extends FileBackend { abstract protected function directoriesAreVirtual(); /** - * Check if a container name is valid. + * Check if a short container name is valid + * + * This checks for length and illegal characters. + * This may disallow certain characters that can appear + * in the prefix used to make the full container name. + * + * @param string $container + * @return bool + */ + final protected static function isValidShortContainerName( $container ) { + // Suffixes like '.xxx' (hex shard chars) or '.seg' (file segments) + // might be used by subclasses. Reserve the dot character for sanity. + // The only way dots end up in containers (e.g. resolveStoragePath) + // is due to the wikiId container prefix or the above suffixes. + return self::isValidContainerName( $container ) && !preg_match( '/[.]/', $container ); + } + + /** + * Check if a full container name is valid + * * This checks for length and illegal characters. + * Limiting the characters makes migrations to other stores easier. * * @param string $container * @return bool */ final protected static function isValidContainerName( $container ) { - // This accounts for Swift and S3 restrictions while leaving room - // for things like '.xxx' (hex shard chars) or '.seg' (segments). - // This disallows directory separators or traversal characters. + // This accounts for NTFS, Swift, and Ceph restrictions + // and disallows directory separators or traversal characters. // Note that matching strings URL encode to the same string; - // in Swift, the length restriction is *after* URL encoding. - return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container ); + // in Swift/Ceph, the length restriction is *after* URL encoding. + return (bool)preg_match( '/^[a-z0-9][a-z0-9-_.]{0,199}$/i', $container ); } /** @@ -1392,17 +1413,17 @@ abstract class FileBackendStore extends FileBackend { * @return array (container, path, container suffix) or (null, null, null) if invalid */ final protected function resolveStoragePath( $storagePath ) { - list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath ); + list( $backend, $shortCont, $relPath ) = self::splitStoragePath( $storagePath ); if ( $backend === $this->name ) { // must be for this backend $relPath = self::normalizeContainerPath( $relPath ); - if ( $relPath !== null ) { + if ( $relPath !== null && self::isValidShortContainerName( $shortCont ) ) { // Get shard for the normalized path if this container is sharded - $cShard = $this->getContainerShard( $container, $relPath ); + $cShard = $this->getContainerShard( $shortCont, $relPath ); // Validate and sanitize the relative path (backend-specific) - $relPath = $this->resolveContainerPath( $container, $relPath ); + $relPath = $this->resolveContainerPath( $shortCont, $relPath ); if ( $relPath !== null ) { // Prepend any wiki ID prefix to the container name - $container = $this->fullContainerName( $container ); + $container = $this->fullContainerName( $shortCont ); if ( self::isValidContainerName( $container ) ) { // Validate and sanitize the container name (backend-specific) $container = $this->resolveContainerName( "{$container}{$cShard}" ); @@ -1592,7 +1613,7 @@ abstract class FileBackendStore extends FileBackend { * @param array $val Information to cache */ final protected function setContainerCache( $container, array $val ) { - $this->memCache->add( $this->containerCacheKey( $container ), $val, 14 * 86400 ); + $this->memCache->set( $this->containerCacheKey( $container ), $val, 14 * 86400 ); } /** @@ -1602,7 +1623,7 @@ abstract class FileBackendStore extends FileBackend { * @param string $container Resolved container name */ final protected function deleteContainerCache( $container ) { - if ( !$this->memCache->set( $this->containerCacheKey( $container ), 'PURGED', 300 ) ) { + if ( !$this->memCache->delete( $this->containerCacheKey( $container ), 300 ) ) { trigger_error( "Unable to delete stat cache for container $container." ); } } @@ -1682,21 +1703,8 @@ abstract class FileBackendStore extends FileBackend { $age = time() - wfTimestamp( TS_UNIX, $val['mtime'] ); $ttl = min( 7 * 86400, max( 300, floor( .1 * $age ) ) ); $key = $this->fileCacheKey( $path ); - // Set the cache unless it is currently salted with the value "PURGED". - // Using add() handles this except it also is a no-op in that case where - // the current value is not "latest" but $val is, so use CAS in that case. - if ( !$this->memCache->add( $key, $val, $ttl ) && !empty( $val['latest'] ) ) { - $this->memCache->merge( - $key, - function ( BagOStuff $cache, $key, $cValue ) use ( $val ) { - return ( is_array( $cValue ) && empty( $cValue['latest'] ) ) - ? $val // update the stat cache with the lastest info - : false; // do nothing (cache is salted or some error happened) - }, - $ttl, - 1 - ); - } + // Set the cache unless it is currently salted. + $this->memCache->set( $key, $val, $ttl ); } /** @@ -1712,7 +1720,7 @@ abstract class FileBackendStore extends FileBackend { if ( $path === null ) { return; // invalid storage path } - if ( !$this->memCache->set( $this->fileCacheKey( $path ), 'PURGED', 300 ) ) { + if ( !$this->memCache->delete( $this->fileCacheKey( $path ), 300 ) ) { trigger_error( "Unable to delete stat cache for file $path." ); } } diff --git a/includes/filebackend/FileOp.php b/includes/filebackend/FileOp.php index 66d87943..5d4e8e11 100644 --- a/includes/filebackend/FileOp.php +++ b/includes/filebackend/FileOp.php @@ -577,9 +577,9 @@ class StoreFileOp extends FileOp { } protected function getSourceSha1Base36() { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $hash = sha1_file( $this->params['src'] ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $hash !== false ) { $hash = wfBaseConvert( $hash, 16, 36, 31 ); } diff --git a/includes/filebackend/MemoryFileBackend.php b/includes/filebackend/MemoryFileBackend.php index 7c2f8256..2879ddde 100644 --- a/includes/filebackend/MemoryFileBackend.php +++ b/includes/filebackend/MemoryFileBackend.php @@ -35,6 +35,10 @@ class MemoryFileBackend extends FileBackendStore { /** @var array Map of (file path => (data,mtime) */ protected $files = array(); + public function getFeatures() { + return self::ATTR_UNICODE_PATHS; + } + public function isPathUsableInternal( $storagePath ) { return true; } @@ -67,9 +71,9 @@ class MemoryFileBackend extends FileBackendStore { return $status; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = file_get_contents( $params['src'] ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $data === false ) { // source doesn't exist? $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php index 5f406c9b..408194f4 100644 --- a/includes/filebackend/SwiftFileBackend.php +++ b/includes/filebackend/SwiftFileBackend.php @@ -128,7 +128,9 @@ class SwiftFileBackend extends FileBackendStore { // HTTP helper client $this->http = new MultiHttpClient( array() ); // Cache container information to mask latency - $this->memCache = wfGetMainCache(); + if ( isset( $config['wanCache'] ) && $config['wanCache'] instanceof WANObjectCache ) { + $this->memCache = $config['wanCache']; + } // Process cache for container info $this->containerStatCache = new ProcessCacheLRU( 300 ); // Cache auth token information to avoid RTTs @@ -136,13 +138,12 @@ class SwiftFileBackend extends FileBackendStore { if ( PHP_SAPI === 'cli' ) { $this->srvCache = wfGetMainCache(); // preferrably memcached } else { - try { // look for APC, XCache, WinCache, ect... - $this->srvCache = ObjectCache::newAccelerator( array() ); - } catch ( Exception $e ) { - } + // look for APC, XCache, WinCache, ect... + $this->srvCache = ObjectCache::newAccelerator( CACHE_NONE ); } + } else { + $this->srvCache = new EmptyBagOStuff(); } - $this->srvCache = $this->srvCache ?: new EmptyBagOStuff(); } public function getFeatures() { @@ -171,30 +172,40 @@ class SwiftFileBackend extends FileBackendStore { /** * Sanitize and filter the custom headers from a $params array. - * We only allow certain Content- and X-Content- headers. + * Only allows certain "standard" Content- and X-Content- headers. * * @param array $params * @return array Sanitized value of 'headers' field in $params */ protected function sanitizeHdrs( array $params ) { + return isset( $params['headers'] ) + ? $this->getCustomHeaders( $params['headers'] ) + : array(); + + } + + /** + * @param array $rawHeaders + * @return array Custom non-metadata HTTP headers + */ + protected function getCustomHeaders( array $rawHeaders ) { $headers = array(); // Normalize casing, and strip out illegal headers - if ( isset( $params['headers'] ) ) { - foreach ( $params['headers'] as $name => $value ) { - $name = strtolower( $name ); - if ( preg_match( '/^content-(type|length)$/', $name ) ) { - continue; // blacklisted - } elseif ( preg_match( '/^(x-)?content-/', $name ) ) { - $headers[$name] = $value; // allowed - } elseif ( preg_match( '/^content-(disposition)/', $name ) ) { - $headers[$name] = $value; // allowed - } + foreach ( $rawHeaders as $name => $value ) { + $name = strtolower( $name ); + if ( preg_match( '/^content-(type|length)$/', $name ) ) { + continue; // blacklisted + } elseif ( preg_match( '/^(x-)?content-/', $name ) ) { + $headers[$name] = $value; // allowed + } elseif ( preg_match( '/^content-(disposition)/', $name ) ) { + $headers[$name] = $value; // allowed } } // By default, Swift has annoyingly low maximum header value limits if ( isset( $headers['content-disposition'] ) ) { $disposition = ''; + // @note: assume FileBackend::makeContentDisposition() already used foreach ( explode( ';', $headers['content-disposition'] ) as $part ) { $part = trim( $part ); $new = ( $disposition === '' ) ? $part : "{$disposition};{$part}"; @@ -210,6 +221,35 @@ class SwiftFileBackend extends FileBackendStore { return $headers; } + /** + * @param array $rawHeaders + * @return array Custom metadata headers + */ + protected function getMetadataHeaders( array $rawHeaders ) { + $headers = array(); + foreach ( $rawHeaders as $name => $value ) { + $name = strtolower( $name ); + if ( strpos( $name, 'x-object-meta-' ) === 0 ) { + $headers[$name] = $value; + } + } + + return $headers; + } + + /** + * @param array $rawHeaders + * @return array Custom metadata headers with prefix removed + */ + protected function getMetadata( array $rawHeaders ) { + $metadata = array(); + foreach ( $this->getMetadataHeaders( $rawHeaders ) as $name => $value ) { + $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value; + } + + return $metadata; + } + protected function doCreateInternal( array $params ) { $status = Status::newGood(); @@ -235,16 +275,16 @@ class SwiftFileBackend extends FileBackendStore { 'body' => $params['content'] ) ); - $be = $this; + $that = $this; $method = __METHOD__; - $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) { + $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response']; if ( $rcode === 201 ) { // good } elseif ( $rcode === 412 ) { $status->fatal( 'backend-fail-contenttype', $params['dst'] ); } else { - $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); + $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); } }; @@ -268,9 +308,9 @@ class SwiftFileBackend extends FileBackendStore { return $status; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $sha1Hash = sha1_file( $params['src'] ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $sha1Hash === false ) { // source doesn't exist? $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] ); @@ -298,16 +338,16 @@ class SwiftFileBackend extends FileBackendStore { 'body' => $handle // resource ) ); - $be = $this; + $that = $this; $method = __METHOD__; - $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) { + $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response']; if ( $rcode === 201 ) { // good } elseif ( $rcode === 412 ) { $status->fatal( 'backend-fail-contenttype', $params['dst'] ); } else { - $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); + $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); } }; @@ -347,16 +387,16 @@ class SwiftFileBackend extends FileBackendStore { ) + $this->sanitizeHdrs( $params ), // extra headers merged into object ) ); - $be = $this; + $that = $this; $method = __METHOD__; - $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) { + $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response']; if ( $rcode === 201 ) { // good } elseif ( $rcode === 404 ) { $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); } else { - $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); + $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); } }; @@ -405,9 +445,9 @@ class SwiftFileBackend extends FileBackendStore { ); } - $be = $this; + $that = $this; $method = __METHOD__; - $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) { + $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response']; if ( $request['method'] === 'PUT' && $rcode === 201 ) { // good @@ -416,7 +456,7 @@ class SwiftFileBackend extends FileBackendStore { } elseif ( $rcode === 404 ) { $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] ); } else { - $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); + $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); } }; @@ -446,9 +486,9 @@ class SwiftFileBackend extends FileBackendStore { 'headers' => array() ) ); - $be = $this; + $that = $this; $method = __METHOD__; - $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) { + $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response']; if ( $rcode === 204 ) { // good @@ -457,7 +497,7 @@ class SwiftFileBackend extends FileBackendStore { $status->fatal( 'backend-fail-delete', $params['src'] ); } } else { - $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); + $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); } }; @@ -505,16 +545,16 @@ class SwiftFileBackend extends FileBackendStore { 'headers' => $metaHdrs + $customHdrs ) ); - $be = $this; + $that = $this; $method = __METHOD__; - $handler = function ( array $request, Status $status ) use ( $be, $method, $params ) { + $handler = function ( array $request, Status $status ) use ( $that, $method, $params ) { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response']; if ( $rcode === 202 ) { // good } elseif ( $rcode === 404 ) { $status->fatal( 'backend-fail-describe', $params['src'] ); } else { - $be->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); + $that->onError( $status, $method, $params, $rerr, $rcode, $rdesc ); } }; @@ -664,17 +704,24 @@ class SwiftFileBackend extends FileBackendStore { return $objHdrs; // nothing to do } + /** @noinspection PhpUnusedLocalVariableInspection */ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" ); - trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING ); + wfDebugLog( 'SwiftBackend', __METHOD__ . ": $path was not stored with SHA-1 metadata." ); + + $objHdrs['x-object-meta-sha1base36'] = false; $auth = $this->getAuthentication(); if ( !$auth ) { - $objHdrs['x-object-meta-sha1base36'] = false; - return $objHdrs; // failed } + // Find prior custom HTTP headers + $postHeaders = $this->getCustomHeaders( $objHdrs ); + // Find prior metadata headers + $postHeaders += $this->getMetadataHeaders( $objHdrs ); + $status = Status::newGood(); + /** @noinspection PhpUnusedLocalVariableInspection */ $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status ); if ( $status->isOK() ) { $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) ); @@ -682,20 +729,24 @@ class SwiftFileBackend extends FileBackendStore { $hash = $tmpFile->getSha1Base36(); if ( $hash !== false ) { $objHdrs['x-object-meta-sha1base36'] = $hash; + // Merge new SHA1 header into the old ones + $postHeaders['x-object-meta-sha1base36'] = $hash; list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $path ); - list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->http->run( array( + list( $rcode ) = $this->http->run( array( 'method' => 'POST', 'url' => $this->storageUrl( $auth, $srcCont, $srcRel ), - 'headers' => $this->authTokenHeaders( $auth ) + $objHdrs + 'headers' => $this->authTokenHeaders( $auth ) + $postHeaders ) ); if ( $rcode >= 200 && $rcode <= 299 ) { + $this->deleteFileCache( $path ); + return $objHdrs; // success } } } } - trigger_error( "Unable to set SHA-1 metadata for $path", E_USER_WARNING ); - $objHdrs['x-object-meta-sha1base36'] = false; + + wfDebugLog( 'SwiftBackend', __METHOD__ . ": unable to set SHA-1 metadata for $path" ); return $objHdrs; // failed } @@ -1544,22 +1595,16 @@ class SwiftFileBackend extends FileBackendStore { */ protected function getStatFromHeaders( array $rhdrs ) { // Fetch all of the custom metadata headers - $metadata = array(); - foreach ( $rhdrs as $name => $value ) { - if ( strpos( $name, 'x-object-meta-' ) === 0 ) { - $metadata[substr( $name, strlen( 'x-object-meta-' ) )] = $value; - } - } + $metadata = $this->getMetadata( $rhdrs ); // Fetch all of the custom raw HTTP headers $headers = $this->sanitizeHdrs( array( 'headers' => $rhdrs ) ); + return array( // Convert various random Swift dates to TS_MW 'mtime' => $this->convertSwiftDate( $rhdrs['last-modified'], TS_MW ), // Empty objects actually return no content-length header in Ceph 'size' => isset( $rhdrs['content-length'] ) ? (int)$rhdrs['content-length'] : 0, - 'sha1' => isset( $rhdrs['x-object-meta-sha1base36'] ) - ? $rhdrs['x-object-meta-sha1base36'] - : null, + 'sha1' => isset( $metadata['sha1base36'] ) ? $metadata['sha1base36'] : null, // Note: manifiest ETags are not an MD5 of the file 'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null, 'xattr' => array( 'metadata' => $metadata, 'headers' => $headers ) diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php index 791be7fc..46b53600 100644 --- a/includes/filebackend/TempFSFile.php +++ b/includes/filebackend/TempFSFile.php @@ -59,15 +59,14 @@ class TempFSFile extends FSFile { $ext = ( $extension != '' ) ? ".{$extension}" : ""; for ( $attempt = 1; true; $attempt++ ) { $path = "{$base}-{$attempt}{$ext}"; - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $newFileHandle = fopen( $path, 'x' ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $newFileHandle ) { fclose( $newFileHandle ); break; // got it } if ( $attempt >= 5 ) { - return null; // give up } } @@ -84,9 +83,9 @@ class TempFSFile extends FSFile { */ public function purge() { $this->canDelete = false; // done - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $ok = unlink( $this->path ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); unset( self::$pathsCollect[$this->path] ); @@ -96,7 +95,7 @@ class TempFSFile extends FSFile { /** * Clean up the temporary file only after an object goes out of scope * - * @param stdClass $object + * @param object $object * @return TempFSFile This object */ public function bind( $object ) { @@ -144,9 +143,9 @@ class TempFSFile extends FSFile { */ public static function purgeAllOnShutdown() { foreach ( self::$pathsCollect as $path ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); unlink( $path ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } } diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php index 39a55635..b81cf3e4 100644 --- a/includes/filebackend/lockmanager/DBLockManager.php +++ b/includes/filebackend/lockmanager/DBLockManager.php @@ -96,7 +96,7 @@ abstract class DBLockManager extends QuorumLockManager { // Tracks peers that couldn't be queried recently to avoid lengthy // connection timeouts. This is useless if each bucket has one peer. try { - $this->statusCache = ObjectCache::newAccelerator( array() ); + $this->statusCache = ObjectCache::newAccelerator(); } catch ( Exception $e ) { trigger_error( __CLASS__ . " using multiple DB peers without apc, xcache, or wincache." ); diff --git a/includes/filebackend/lockmanager/FSLockManager.php b/includes/filebackend/lockmanager/FSLockManager.php index bce6b34c..6f46f0e4 100644 --- a/includes/filebackend/lockmanager/FSLockManager.php +++ b/includes/filebackend/lockmanager/FSLockManager.php @@ -117,9 +117,9 @@ class FSLockManager extends LockManager { if ( isset( $this->handles[$path] ) ) { $handle = $this->handles[$path]; } else { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $handle = fopen( $this->getLockPath( $path ), 'a+' ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$handle ) { // lock dir missing? wfMkdirParents( $this->lockDir ); $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php index 615ba77e..8115fd42 100644 --- a/includes/filebackend/lockmanager/LockManager.php +++ b/includes/filebackend/lockmanager/LockManager.php @@ -102,7 +102,6 @@ abstract class LockManager { * @since 1.22 */ final public function lockByType( array $pathsByType, $timeout = 0 ) { - $status = Status::newGood(); $pathsByType = $this->normalizePathsByType( $pathsByType ); $msleep = array( 0, 50, 100, 300, 500 ); // retry backoff times $start = microtime( true ); |