r105819 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r105818‎ | r105819 | r105820 >
Date:19:23, 11 December 2011
Author:aaron
Status:deferred
Tags:
Comment:
In LockManager:
* Added LockManager::LOCK_UW for readers that are using the data to write elsewhere.
In DBLockManager:
* Removed broken shared lock query.
* Split out MySQL lock manager subclass with shared locks (that don't lock the whole table).
* Only call cacheRecordFailure() if we have a DB connection error.
Modified paths:
  • /branches/FileBackend/phase3/includes/AutoLoader.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php (modified) (history)

Diff [purge]

Index: branches/FileBackend/phase3/includes/filerepo/backend/LockManager.php
@@ -3,18 +3,28 @@
44 * FileBackend helper class for handling file locking.
55 * Locks on resource keys can either be shared or exclusive.
66 *
7 - * Implementations can keep track of what is locked in the process cache.
8 - * This can reduce hits to external resources for lock()/unlock() calls.
 7+ * Implementations must keep track of what is locked by this proccess
 8+ * in-memory and support nested locking calls (using reference counting).
 9+ * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
 10+ * Locks should either be non-blocking or have low wait timeouts.
911 *
1012 * Subclasses should avoid throwing exceptions at all costs.
1113 *
1214 * @ingroup FileBackend
1315 */
1416 abstract class LockManager {
15 - /* Lock types; stronger locks have high values */
 17+ /* Lock types; stronger locks have higher values */
1618 const LOCK_SH = 1; // shared lock (for reads)
17 - const LOCK_EX = 2; // exclusive lock (for writes)
 19+ const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
 20+ const LOCK_EX = 3; // exclusive lock (for writes)
1821
 22+ /** @var Array Mapping of lock types to the type actually used */
 23+ protected $lockTypeMap = array(
 24+ self::LOCK_SH => self::LOCK_SH,
 25+ self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
 26+ self::LOCK_EX => self::LOCK_EX
 27+ );
 28+
1929 /**
2030 * Construct a new instance from configuration
2131 *
@@ -26,31 +36,31 @@
2737 * Lock the resources at the given abstract paths
2838 *
2939 * @param $paths Array List of resource names
30 - * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
 40+ * @param $type integer LockManager::LOCK_* constant
3141 * @return Status
3242 */
3343 final public function lock( array $paths, $type = self::LOCK_EX ) {
3444 $keys = array_unique( array_map( 'sha1', $paths ) );
35 - return $this->doLock( $keys, $type );
 45+ return $this->doLock( $keys, $this->lockTypeMap[$type] );
3646 }
3747
3848 /**
3949 * Unlock the resources at the given abstract paths
4050 *
4151 * @param $paths Array List of storage paths
42 - * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
 52+ * @param $type integer LockManager::LOCK_* constant
4353 * @return Status
4454 */
4555 final public function unlock( array $paths, $type = self::LOCK_EX ) {
4656 $keys = array_unique( array_map( 'sha1', $paths ) );
47 - return $this->doUnlock( $keys, $type );
 57+ return $this->doUnlock( $keys, $this->lockTypeMap[$type] );
4858 }
4959
5060 /**
5161 * Lock resources with the given keys and lock type
5262 *
5363 * @param $key Array List of keys to lock (40 char hex hashes)
54 - * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
 64+ * @param $type integer LockManager::LOCK_* constant
5565 * @return string
5666 */
5767 abstract protected function doLock( array $keys, $type );
@@ -59,7 +69,7 @@
6070 * Unlock resources with the given keys and lock type
6171 *
6272 * @param $key Array List of keys to unlock (40 char hex hashes)
63 - * @param $type integer LockManager::LOCK_EX, LockManager::LOCK_SH
 73+ * @param $type integer LockManager::LOCK_* constant
6474 * @return string
6575 */
6676 abstract protected function doUnlock( array $keys, $type );
@@ -74,6 +84,13 @@
7585 * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
7686 */
7787 class FSLockManager extends LockManager {
 88+ /** @var Array Mapping of lock types to the type actually used */
 89+ protected $lockTypeMap = array(
 90+ self::LOCK_SH => self::LOCK_SH,
 91+ self::LOCK_UW => self::LOCK_SH,
 92+ self::LOCK_EX => self::LOCK_EX
 93+ );
 94+
7895 protected $lockDir; // global dir for all servers
7996
8097 /** @var Array Map of (locked key => lock type => count) */
@@ -259,7 +276,7 @@
260277 *
261278 * All lock requests for a resource, identified by a hash string, will
262279 * map to one bucket. Each bucket maps to one or several peer DB servers,
263 - * each having a `file_locks` table with row-level locking.
 280+ * each having a the file_locks.sql tables with row-level locking.
264281 * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
265282 *
266283 * A majority of peer servers must agree for a lock to be acquired.
@@ -284,7 +301,7 @@
285302
286303 /** @var Array Map of (locked key => lock type => count) */
287304 protected $locksHeld = array();
288 - /** $var Array Map Lock-active database connections (server name => Database) */
 305+ /** @var Array Map Lock-active database connections (DB name => Database) */
289306 protected $activeConns = array();
290307
291308 /**
@@ -363,7 +380,7 @@
364381 $status->merge( $this->doUnlock( $lockedKeys, $type ) );
365382 return $status;
366383 } elseif ( $res !== true ) {
367 - // Couldn't contact any servers for this bucket.
 384+ // Couldn't contact any DBs for this bucket.
368385 // Abort and unlock everything we just locked.
369386 $status->fatal( 'lockmanager-fail-db-bucket', $bucket );
370387 $status->merge( $this->doUnlock( $lockedKeys, $type ) );
@@ -408,47 +425,26 @@
409426 }
410427
411428 /**
412 - * Get a DB connection to a lock server and acquire locks on $keys.
 429+ * Get a connection to a lock DB and acquire locks on $keys
413430 *
414431 * @param $server string
415432 * @param $keys Array
416433 * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
417 - * @return void
 434+ * @return bool Resources able to be locked
 435+ * @throws DBError
418436 */
419437 protected function doLockingQuery( $server, array $keys, $type ) {
420 - if ( !isset( $this->activeConns[$server] ) ) {
421 - $this->activeConns[$server] = wfGetDB( DB_MASTER, array(), $server );
422 - $this->activeConns[$server]->begin(); // start transaction
423 - # If the connection drops, try to avoid letting the DB rollback
424 - # and release the locks before the file operations are finished.
425 - # This won't handle the case of server reboots however.
426 - $options = array();
427 - if ( php_sapi_name() == 'cli' ) { // maintenance scripts
428 - if ( $this->cliTimeout > 0 ) {
429 - $options['connTimeout'] = $this->cliTimeout;
430 - }
431 - } else { // web requests
432 - if ( $this->webTimeout > 0 ) {
433 - $options['connTimeout'] = $this->webTimeout;
434 - }
435 - }
436 - $this->activeConns[$server]->setSessionOptions( $options );
437 - }
438 - $db = $this->activeConns[$server];
439 - # Try to get the locks...this should be the last query of this function
440 - if ( $type == self::LOCK_SH ) { // reader locks
441 - $db->select( 'file_locks', '1',
442 - array( 'fl_key' => $keys ),
443 - __METHOD__,
444 - array( 'LOCK IN SHARE MODE' ) // single-row gap locks
445 - );
446 - } else { // writer locks
 438+ if ( $type == self::LOCK_EX ) { // writer locks
 439+ $db = $this->getConnection( $server );
 440+ # Actually do the locking queries...
447441 $data = array();
448442 foreach ( $keys as $key ) {
449 - $data[] = array( 'fl_key' => $key );
 443+ $data[] = array( 'fle_key' => $key );
450444 }
451 - $db->insert( 'file_locks', $data, __METHOD__ );
 445+ # Wait on any existing writers and block new ones if we get in
 446+ $db->insert( 'file_locks_exclusive', $data, __METHOD__ );
452447 }
 448+ return true;
453449 }
454450
455451 /**
@@ -462,7 +458,7 @@
463459 */
464460 protected function doLockingQueryAll( $bucket, array $keys, $type ) {
465461 $yesVotes = 0; // locks made on trustable servers
466 - $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining servers
 462+ $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining DBs
467463 $quorum = floor( $votesLeft/2 + 1 ); // simple majority
468464 // Get votes for each server, in order, until we have enough...
469465 foreach ( $this->dbsByBucket[$bucket] as $index => $server ) {
@@ -478,7 +474,9 @@
479475 if ( $this->cacheCheckFailures( $server ) ) {
480476 try {
481477 // Attempt to acquire the lock on this server
482 - $this->doLockingQuery( $server, $keys, $type );
 478+ if ( !$this->doLockingQuery( $server, $keys, $type ) ) {
 479+ return 'cantacquire'; // vetoed; resource locked
 480+ }
483481 // Check that server has no signs of lock loss
484482 if ( $this->checkUptime( $server ) ) {
485483 ++$yesVotes; // success for this peer
@@ -490,11 +488,11 @@
491489 return true; // lock obtained
492490 }
493491 }
 492+ } catch ( DBConnectionError $e ) {
 493+ $this->cacheRecordFailure( $server );
494494 } catch ( DBError $e ) {
495495 if ( $this->lastErrorIndicatesLocked( $server ) ) {
496496 return 'cantacquire'; // vetoed; resource locked
497 - } else { // can't connect?
498 - $this->cacheRecordFailure( $server );
499497 }
500498 }
501499 }
@@ -513,6 +511,46 @@
514512 }
515513
516514 /**
 515+ * Get a new connection to a lock DB
 516+ *
 517+ * @param $server string
 518+ * @return Database
 519+ * @throws DBError
 520+ */
 521+ protected function getConnection( $server ) {
 522+ if ( !isset( $this->activeConns[$server] ) ) {
 523+ $this->activeConns[$server] = wfGetDB( DB_MASTER, array(), $server );
 524+ $this->activeConns[$server]->begin(); // start transaction
 525+ # If the connection drops, try to avoid letting the DB rollback
 526+ # and release the locks before the file operations are finished.
 527+ # This won't handle the case of server reboots however.
 528+ $options = array();
 529+ if ( php_sapi_name() == 'cli' ) { // maintenance scripts
 530+ if ( $this->cliTimeout > 0 ) {
 531+ $options['connTimeout'] = $this->cliTimeout;
 532+ }
 533+ } else { // web requests
 534+ if ( $this->webTimeout > 0 ) {
 535+ $options['connTimeout'] = $this->webTimeout;
 536+ }
 537+ }
 538+ $this->activeConns[$server]->setSessionOptions( $options );
 539+ $this->initConnection( $server, $this->activeConns[$server] );
 540+ }
 541+ return $this->activeConns[$server];
 542+ }
 543+
 544+ /**
 545+ * Do additional initialization for new lock DB connection
 546+ *
 547+ * @param $server string
 548+ * @param $db Database
 549+ * @return void
 550+ * @throws DBError
 551+ */
 552+ protected function initConnection( $server, DatabaseBase $db ) {}
 553+
 554+ /**
517555 * Commit all changes to lock-active databases.
518556 * This should avoid throwing any exceptions.
519557 *
@@ -554,6 +592,7 @@
555593 *
556594 * @param $server string
557595 * @return bool
 596+ * @throws DBError
558597 */
559598 protected function checkUptime( $server ) {
560599 if ( isset( $this->activeConns[$server] ) ) { // sanity
@@ -679,12 +718,70 @@
680719 return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->dbsByBucket );
681720 }
682721
 722+ /**
 723+ * Make sure remaining locks get cleared for sanity
 724+ */
683725 function __destruct() {
684 - // Make sure remaining locks get cleared for sanity
685726 $this->finishLockTransactions();
686727 }
687728 }
688729
 730+class MySqlLockManager extends DBLockManager {
 731+ /** @var Array Mapping of lock types to the type actually used */
 732+ protected $lockTypeMap = array(
 733+ self::LOCK_SH => self::LOCK_SH,
 734+ self::LOCK_UW => self::LOCK_SH,
 735+ self::LOCK_EX => self::LOCK_EX
 736+ );
 737+
 738+ /** @var Array Map of (DB name => original transaction isolation) */
 739+ protected $trxIso = array();
 740+
 741+ protected function initConnection( $server, DatabaseBase $db ) {
 742+ # Get the original transaction level for the server.
 743+ $row = $db->query( "SELECT @@tx_isolation AS tx_iso;" )->fetchObject();
 744+ # Convert "REPEATABLE-READ" => "REPEATABLE READ" for SET query
 745+ $this->trxIso[$server] = str_replace( '-', ' ', $row->tx_iso );
 746+ }
 747+
 748+ protected function doLockingQuery( $server, array $keys, $type ) {
 749+ $ok = true;
 750+ # Actually do the locking queries...
 751+ if ( $type == self::LOCK_SH ) { // reader locks
 752+ $db = $this->getConnection( $server );
 753+ $data = array();
 754+ foreach ( $keys as $key ) {
 755+ $data[] = array( 'fls_key' => $key );
 756+ }
 757+ # Block new writers...
 758+ $db->insert( 'file_locks_shared', $data, __METHOD__ );
 759+ # Wait on any existing writers...
 760+ $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED" );
 761+ $ok = !$db->selectField( 'file_locks_exclusive', '1',
 762+ array( 'fle_key' => $keys ),
 763+ __METHOD__
 764+ );
 765+ $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL {$this->trxIso[$server]}" );
 766+ } elseif ( $type == self::LOCK_EX ) { // writer locks
 767+ $db = $this->getConnection( $server );
 768+ $data = array();
 769+ foreach ( $keys as $key ) {
 770+ $data[] = array( 'fle_key' => $key );
 771+ }
 772+ # Block new readers/writers and wait on any existing writers
 773+ $db->insert( 'file_locks_exclusive', $data, __METHOD__ );
 774+ # Wait on any existing readers...
 775+ $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED" );
 776+ $ok = !$db->selectField( 'file_locks_shared', '1',
 777+ array( 'fls_key' => $keys ),
 778+ __METHOD__
 779+ );
 780+ $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL {$this->trxIso[$server]}" );
 781+ }
 782+ return $ok;
 783+ }
 784+}
 785+
689786 /**
690787 * Simple version of LockManager that does nothing
691788 */
Index: branches/FileBackend/phase3/includes/AutoLoader.php
@@ -499,6 +499,7 @@
500500 'LockManager' => 'includes/filerepo/backend/LockManager.php',
501501 'FSLockManager' => 'includes/filerepo/backend/LockManager.php',
502502 'DBLockManager' => 'includes/filerepo/backend/LockManager.php',
 503+ 'MySqlLockManager'=> 'includes/filerepo/backend/LockManager.php',
503504 'NullLockManager' => 'includes/filerepo/backend/LockManager.php',
504505 'FileOp' => 'includes/filerepo/backend/FileOp.php',
505506 'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',

Follow-up revisions

RevisionCommit summaryAuthorDate
r105845FU r105819: missing sql file changeaaron00:50, 12 December 2011

Status & tagging log