MediaWiki
REL1_19
|
00001 <?php 00023 class FileBackendMultiWrite extends FileBackend { 00025 protected $backends = array(); // array of (backend index => backends) 00026 protected $masterIndex = -1; // integer; index of master backend 00027 protected $syncChecks = 0; // integer bitfield 00028 00029 /* Possible internal backend consistency checks */ 00030 const CHECK_SIZE = 1; 00031 const CHECK_TIME = 2; 00032 00046 public function __construct( array $config ) { 00047 parent::__construct( $config ); 00048 $namesUsed = array(); 00049 // Construct backends here rather than via registration 00050 // to keep these backends hidden from outside the proxy. 00051 foreach ( $config['backends'] as $index => $config ) { 00052 $name = $config['name']; 00053 if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates 00054 throw new MWException( "Two or more backends defined with the name $name." ); 00055 } 00056 $namesUsed[$name] = 1; 00057 if ( !isset( $config['class'] ) ) { 00058 throw new MWException( 'No class given for a backend config.' ); 00059 } 00060 $class = $config['class']; 00061 $this->backends[$index] = new $class( $config ); 00062 if ( !empty( $config['isMultiMaster'] ) ) { 00063 if ( $this->masterIndex >= 0 ) { 00064 throw new MWException( 'More than one master backend defined.' ); 00065 } 00066 $this->masterIndex = $index; 00067 } 00068 } 00069 if ( $this->masterIndex < 0 ) { // need backends and must have a master 00070 throw new MWException( 'No master backend defined.' ); 00071 } 00072 $this->syncChecks = isset( $config['syncChecks'] ) 00073 ? $config['syncChecks'] 00074 : self::CHECK_SIZE; 00075 } 00076 00080 final protected function doOperationsInternal( array $ops, array $opts ) { 00081 $status = Status::newGood(); 00082 00083 $performOps = array(); // list of FileOp objects 00084 $filesRead = $filesChanged = array(); // storage paths used 00085 // Build up a list of FileOps. The list will have all the ops 00086 // for one backend, then all the ops for the next, and so on. 00087 // These batches of ops are all part of a continuous array. 00088 // Also build up a list of files read/changed... 00089 foreach ( $this->backends as $index => $backend ) { 00090 $backendOps = $this->substOpBatchPaths( $ops, $backend ); 00091 // Add on the operation batch for this backend 00092 $performOps = array_merge( $performOps, $backend->getOperations( $backendOps ) ); 00093 if ( $index == 0 ) { // first batch 00094 // Get the files used for these operations. Each backend has a batch of 00095 // the same operations, so we only need to get them from the first batch. 00096 foreach ( $performOps as $fileOp ) { 00097 $filesRead = array_merge( $filesRead, $fileOp->storagePathsRead() ); 00098 $filesChanged = array_merge( $filesChanged, $fileOp->storagePathsChanged() ); 00099 } 00100 // Get the paths under the proxy backend's name 00101 $filesRead = $this->unsubstPaths( $filesRead ); 00102 $filesChanged = $this->unsubstPaths( $filesChanged ); 00103 } 00104 } 00105 00106 // Try to lock those files for the scope of this function... 00107 if ( empty( $opts['nonLocking'] ) ) { 00108 $filesLockSh = array_diff( $filesRead, $filesChanged ); // optimization 00109 $filesLockEx = $filesChanged; 00110 // Get a shared lock on the parent directory of each path changed 00111 $filesLockSh = array_merge( $filesLockSh, array_map( 'dirname', $filesLockEx ) ); 00112 // Try to lock those files for the scope of this function... 00113 $scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager::LOCK_UW, $status ); 00114 $scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status ); 00115 if ( !$status->isOK() ) { 00116 return $status; // abort 00117 } 00118 } 00119 00120 // Clear any cache entries (after locks acquired) 00121 $this->clearCache(); 00122 00123 // Do a consistency check to see if the backends agree 00124 if ( count( $this->backends ) > 1 ) { 00125 $status->merge( $this->consistencyCheck( array_merge( $filesRead, $filesChanged ) ) ); 00126 if ( !$status->isOK() ) { 00127 return $status; // abort 00128 } 00129 } 00130 00131 // Actually attempt the operation batch... 00132 $subStatus = FileOp::attemptBatch( $performOps, $opts ); 00133 00134 $success = array(); 00135 $failCount = $successCount = 0; 00136 // Make 'success', 'successCount', and 'failCount' fields reflect 00137 // the overall operation, rather than all the batches for each backend. 00138 // Do this by only using success values from the master backend's batch. 00139 $batchStart = $this->masterIndex * count( $ops ); 00140 $batchEnd = $batchStart + count( $ops ) - 1; 00141 for ( $i = $batchStart; $i <= $batchEnd; $i++ ) { 00142 if ( !isset( $subStatus->success[$i] ) ) { 00143 break; // failed out before trying this op 00144 } elseif ( $subStatus->success[$i] ) { 00145 ++$successCount; 00146 } else { 00147 ++$failCount; 00148 } 00149 $success[] = $subStatus->success[$i]; 00150 } 00151 $subStatus->success = $success; 00152 $subStatus->successCount = $successCount; 00153 $subStatus->failCount = $failCount; 00154 00155 // Merge errors into status fields 00156 $status->merge( $subStatus ); 00157 $status->success = $subStatus->success; // not done in merge() 00158 00159 return $status; 00160 } 00161 00168 public function consistencyCheck( array $paths ) { 00169 $status = Status::newGood(); 00170 if ( $this->syncChecks == 0 ) { 00171 return $status; // skip checks 00172 } 00173 00174 $mBackend = $this->backends[$this->masterIndex]; 00175 foreach ( array_unique( $paths ) as $path ) { 00176 $params = array( 'src' => $path, 'latest' => true ); 00177 // Stat the file on the 'master' backend 00178 $mStat = $mBackend->getFileStat( $this->substOpPaths( $params, $mBackend ) ); 00179 // Check of all clone backends agree with the master... 00180 foreach ( $this->backends as $index => $cBackend ) { 00181 if ( $index === $this->masterIndex ) { 00182 continue; // master 00183 } 00184 $cStat = $cBackend->getFileStat( $this->substOpPaths( $params, $cBackend ) ); 00185 if ( $mStat ) { // file is in master 00186 if ( !$cStat ) { // file should exist 00187 $status->fatal( 'backend-fail-synced', $path ); 00188 continue; 00189 } 00190 if ( $this->syncChecks & self::CHECK_SIZE ) { 00191 if ( $cStat['size'] != $mStat['size'] ) { // wrong size 00192 $status->fatal( 'backend-fail-synced', $path ); 00193 continue; 00194 } 00195 } 00196 if ( $this->syncChecks & self::CHECK_TIME ) { 00197 $mTs = wfTimestamp( TS_UNIX, $mStat['mtime'] ); 00198 $cTs = wfTimestamp( TS_UNIX, $cStat['mtime'] ); 00199 if ( abs( $mTs - $cTs ) > 30 ) { // outdated file somewhere 00200 $status->fatal( 'backend-fail-synced', $path ); 00201 continue; 00202 } 00203 } 00204 } else { // file is not in master 00205 if ( $cStat ) { // file should not exist 00206 $status->fatal( 'backend-fail-synced', $path ); 00207 } 00208 } 00209 } 00210 } 00211 00212 return $status; 00213 } 00214 00223 protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) { 00224 $newOps = array(); // operations 00225 foreach ( $ops as $op ) { 00226 $newOp = $op; // operation 00227 foreach ( array( 'src', 'srcs', 'dst', 'dir' ) as $par ) { 00228 if ( isset( $newOp[$par] ) ) { // string or array 00229 $newOp[$par] = $this->substPaths( $newOp[$par], $backend ); 00230 } 00231 } 00232 $newOps[] = $newOp; 00233 } 00234 return $newOps; 00235 } 00236 00244 protected function substOpPaths( array $ops, FileBackendStore $backend ) { 00245 $newOps = $this->substOpBatchPaths( array( $ops ), $backend ); 00246 return $newOps[0]; 00247 } 00248 00256 protected function substPaths( $paths, FileBackendStore $backend ) { 00257 return preg_replace( 00258 '!^mwstore://' . preg_quote( $this->name ) . '/!', 00259 StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ), 00260 $paths // string or array 00261 ); 00262 } 00263 00270 protected function unsubstPaths( $paths ) { 00271 return preg_replace( 00272 '!^mwstore://([^/]+)!', 00273 StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ), 00274 $paths // string or array 00275 ); 00276 } 00277 00281 public function doPrepare( array $params ) { 00282 $status = Status::newGood(); 00283 foreach ( $this->backends as $backend ) { 00284 $realParams = $this->substOpPaths( $params, $backend ); 00285 $status->merge( $backend->doPrepare( $realParams ) ); 00286 } 00287 return $status; 00288 } 00289 00293 public function doSecure( array $params ) { 00294 $status = Status::newGood(); 00295 foreach ( $this->backends as $backend ) { 00296 $realParams = $this->substOpPaths( $params, $backend ); 00297 $status->merge( $backend->doSecure( $realParams ) ); 00298 } 00299 return $status; 00300 } 00301 00305 public function doClean( array $params ) { 00306 $status = Status::newGood(); 00307 foreach ( $this->backends as $backend ) { 00308 $realParams = $this->substOpPaths( $params, $backend ); 00309 $status->merge( $backend->doClean( $realParams ) ); 00310 } 00311 return $status; 00312 } 00313 00317 public function concatenate( array $params ) { 00318 // We are writing to an FS file, so we don't need to do this per-backend 00319 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00320 return $this->backends[$this->masterIndex]->concatenate( $realParams ); 00321 } 00322 00326 public function fileExists( array $params ) { 00327 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00328 return $this->backends[$this->masterIndex]->fileExists( $realParams ); 00329 } 00330 00334 public function getFileTimestamp( array $params ) { 00335 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00336 return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams ); 00337 } 00338 00342 public function getFileSize( array $params ) { 00343 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00344 return $this->backends[$this->masterIndex]->getFileSize( $realParams ); 00345 } 00346 00350 public function getFileStat( array $params ) { 00351 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00352 return $this->backends[$this->masterIndex]->getFileStat( $realParams ); 00353 } 00354 00358 public function getFileContents( array $params ) { 00359 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00360 return $this->backends[$this->masterIndex]->getFileContents( $realParams ); 00361 } 00362 00366 public function getFileSha1Base36( array $params ) { 00367 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00368 return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams ); 00369 } 00370 00374 public function getFileProps( array $params ) { 00375 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00376 return $this->backends[$this->masterIndex]->getFileProps( $realParams ); 00377 } 00378 00382 public function streamFile( array $params ) { 00383 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00384 return $this->backends[$this->masterIndex]->streamFile( $realParams ); 00385 } 00386 00390 public function getLocalReference( array $params ) { 00391 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00392 return $this->backends[$this->masterIndex]->getLocalReference( $realParams ); 00393 } 00394 00398 public function getLocalCopy( array $params ) { 00399 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00400 return $this->backends[$this->masterIndex]->getLocalCopy( $realParams ); 00401 } 00402 00406 public function getFileList( array $params ) { 00407 $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] ); 00408 return $this->backends[$this->masterIndex]->getFileList( $realParams ); 00409 } 00410 00414 public function clearCache( array $paths = null ) { 00415 foreach ( $this->backends as $backend ) { 00416 $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null; 00417 $backend->clearCache( $realPaths ); 00418 } 00419 } 00420 }