MediaWiki
REL1_20
|
00001 <?php 00040 class DBLockManager extends QuorumLockManager { 00042 protected $dbServers; // (DB name => server config array) 00044 protected $statusCache; 00045 00046 protected $lockExpiry; // integer number of seconds 00047 protected $safeDelay; // integer number of seconds 00048 00049 protected $session = 0; // random integer 00051 protected $conns = array(); 00052 00076 public function __construct( array $config ) { 00077 parent::__construct( $config ); 00078 00079 $this->dbServers = isset( $config['dbServers'] ) 00080 ? $config['dbServers'] 00081 : array(); // likely just using 'localDBMaster' 00082 // Sanitize srvsByBucket config to prevent PHP errors 00083 $this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' ); 00084 $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive 00085 00086 if ( isset( $config['lockExpiry'] ) ) { 00087 $this->lockExpiry = $config['lockExpiry']; 00088 } else { 00089 $met = ini_get( 'max_execution_time' ); 00090 $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0 00091 } 00092 $this->safeDelay = ( $this->lockExpiry <= 0 ) 00093 ? 60 // pick a safe-ish number to match DB timeout default 00094 : $this->lockExpiry; // cover worst case 00095 00096 foreach ( $this->srvsByBucket as $bucket ) { 00097 if ( count( $bucket ) > 1 ) { // multiple peers 00098 // Tracks peers that couldn't be queried recently to avoid lengthy 00099 // connection timeouts. This is useless if each bucket has one peer. 00100 try { 00101 $this->statusCache = ObjectCache::newAccelerator( array() ); 00102 } catch ( MWException $e ) { 00103 trigger_error( __CLASS__ . 00104 " using multiple DB peers without apc, xcache, or wincache." ); 00105 } 00106 break; 00107 } 00108 } 00109 00110 $this->session = wfRandomString( 31 ); 00111 } 00112 00120 protected function getLocksOnServer( $lockSrv, array $paths, $type ) { 00121 $status = Status::newGood(); 00122 00123 if ( $type == self::LOCK_EX ) { // writer locks 00124 try { 00125 $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); 00126 # Build up values for INSERT clause 00127 $data = array(); 00128 foreach ( $keys as $key ) { 00129 $data[] = array( 'fle_key' => $key ); 00130 } 00131 # Wait on any existing writers and block new ones if we get in 00132 $db = $this->getConnection( $lockSrv ); // checked in isServerUp() 00133 $db->insert( 'filelocks_exclusive', $data, __METHOD__ ); 00134 } catch ( DBError $e ) { 00135 foreach ( $paths as $path ) { 00136 $status->fatal( 'lockmanager-fail-acquirelock', $path ); 00137 } 00138 } 00139 } 00140 00141 return $status; 00142 } 00143 00148 protected function freeLocksOnServer( $lockSrv, array $paths, $type ) { 00149 return Status::newGood(); // not supported 00150 } 00151 00156 protected function releaseAllLocks() { 00157 $status = Status::newGood(); 00158 00159 foreach ( $this->conns as $lockDb => $db ) { 00160 if ( $db->trxLevel() ) { // in transaction 00161 try { 00162 $db->rollback( __METHOD__ ); // finish transaction and kill any rows 00163 } catch ( DBError $e ) { 00164 $status->fatal( 'lockmanager-fail-db-release', $lockDb ); 00165 } 00166 } 00167 } 00168 00169 return $status; 00170 } 00171 00176 protected function isServerUp( $lockSrv ) { 00177 if ( !$this->cacheCheckFailures( $lockSrv ) ) { 00178 return false; // recent failure to connect 00179 } 00180 try { 00181 $this->getConnection( $lockSrv ); 00182 } catch ( DBError $e ) { 00183 $this->cacheRecordFailure( $lockSrv ); 00184 return false; // failed to connect 00185 } 00186 return true; 00187 } 00188 00196 protected function getConnection( $lockDb ) { 00197 if ( !isset( $this->conns[$lockDb] ) ) { 00198 $db = null; 00199 if ( $lockDb === 'localDBMaster' ) { 00200 $lb = wfGetLBFactory()->newMainLB(); 00201 $db = $lb->getConnection( DB_MASTER ); 00202 } elseif ( isset( $this->dbServers[$lockDb] ) ) { 00203 $config = $this->dbServers[$lockDb]; 00204 $db = DatabaseBase::factory( $config['type'], $config ); 00205 } 00206 if ( !$db ) { 00207 return null; // config error? 00208 } 00209 $this->conns[$lockDb] = $db; 00210 $this->conns[$lockDb]->clearFlag( DBO_TRX ); 00211 # If the connection drops, try to avoid letting the DB rollback 00212 # and release the locks before the file operations are finished. 00213 # This won't handle the case of DB server restarts however. 00214 $options = array(); 00215 if ( $this->lockExpiry > 0 ) { 00216 $options['connTimeout'] = $this->lockExpiry; 00217 } 00218 $this->conns[$lockDb]->setSessionOptions( $options ); 00219 $this->initConnection( $lockDb, $this->conns[$lockDb] ); 00220 } 00221 if ( !$this->conns[$lockDb]->trxLevel() ) { 00222 $this->conns[$lockDb]->begin( __METHOD__ ); // start transaction 00223 } 00224 return $this->conns[$lockDb]; 00225 } 00226 00235 protected function initConnection( $lockDb, DatabaseBase $db ) {} 00236 00244 protected function cacheCheckFailures( $lockDb ) { 00245 return ( $this->statusCache && $this->safeDelay > 0 ) 00246 ? !$this->statusCache->get( $this->getMissKey( $lockDb ) ) 00247 : true; 00248 } 00249 00256 protected function cacheRecordFailure( $lockDb ) { 00257 return ( $this->statusCache && $this->safeDelay > 0 ) 00258 ? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay ) 00259 : true; 00260 } 00261 00268 protected function getMissKey( $lockDb ) { 00269 $lockDb = ( $lockDb === 'localDBMaster' ) ? wfWikiID() : $lockDb; // non-relative 00270 return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb ); 00271 } 00272 00276 function __destruct() { 00277 foreach ( $this->conns as $db ) { 00278 if ( $db->trxLevel() ) { // in transaction 00279 try { 00280 $db->rollback( __METHOD__ ); // finish transaction and kill any rows 00281 } catch ( DBError $e ) { 00282 // oh well 00283 } 00284 } 00285 $db->close(); 00286 } 00287 } 00288 } 00289 00296 class MySqlLockManager extends DBLockManager { 00298 protected $lockTypeMap = array( 00299 self::LOCK_SH => self::LOCK_SH, 00300 self::LOCK_UW => self::LOCK_SH, 00301 self::LOCK_EX => self::LOCK_EX 00302 ); 00303 00308 protected function initConnection( $lockDb, DatabaseBase $db ) { 00309 # Let this transaction see lock rows from other transactions 00310 $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" ); 00311 } 00312 00320 protected function getLocksOnServer( $lockSrv, array $paths, $type ) { 00321 $status = Status::newGood(); 00322 00323 $db = $this->getConnection( $lockSrv ); // checked in isServerUp() 00324 $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) ); 00325 # Build up values for INSERT clause 00326 $data = array(); 00327 foreach ( $keys as $key ) { 00328 $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session ); 00329 } 00330 # Block new writers... 00331 $db->insert( 'filelocks_shared', $data, __METHOD__, array( 'IGNORE' ) ); 00332 # Actually do the locking queries... 00333 if ( $type == self::LOCK_SH ) { // reader locks 00334 # Bail if there are any existing writers... 00335 $blocked = $db->selectField( 'filelocks_exclusive', '1', 00336 array( 'fle_key' => $keys ), 00337 __METHOD__ 00338 ); 00339 # Prospective writers that haven't yet updated filelocks_exclusive 00340 # will recheck filelocks_shared after doing so and bail due to our entry. 00341 } else { // writer locks 00342 $encSession = $db->addQuotes( $this->session ); 00343 # Bail if there are any existing writers... 00344 # The may detect readers, but the safe check for them is below. 00345 # Note: if two writers come at the same time, both bail :) 00346 $blocked = $db->selectField( 'filelocks_shared', '1', 00347 array( 'fls_key' => $keys, "fls_session != $encSession" ), 00348 __METHOD__ 00349 ); 00350 if ( !$blocked ) { 00351 # Build up values for INSERT clause 00352 $data = array(); 00353 foreach ( $keys as $key ) { 00354 $data[] = array( 'fle_key' => $key ); 00355 } 00356 # Block new readers/writers... 00357 $db->insert( 'filelocks_exclusive', $data, __METHOD__ ); 00358 # Bail if there are any existing readers... 00359 $blocked = $db->selectField( 'filelocks_shared', '1', 00360 array( 'fls_key' => $keys, "fls_session != $encSession" ), 00361 __METHOD__ 00362 ); 00363 } 00364 } 00365 00366 if ( $blocked ) { 00367 foreach ( $paths as $path ) { 00368 $status->fatal( 'lockmanager-fail-acquirelock', $path ); 00369 } 00370 } 00371 00372 return $status; 00373 } 00374 }