r106554 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r106553‎ | r106554 | r106555 >
Date:08:16, 18 December 2011
Author:aaron
Status:deferred
Tags:
Comment:
* Added LSLockManager and and corresponding LockServerDaemon.php file
* Message tweaks
Modified paths:
  • /branches/FileBackend/phase3/includes/AutoLoader.php (modified) (history)
  • /branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php (added) (history)
  • /branches/FileBackend/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /branches/FileBackend/phase3/maintenance/language/messages.inc (modified) (history)
  • /branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php (added) (history)

Diff [purge]

Index: branches/FileBackend/phase3/maintenance/language/messages.inc
@@ -1374,7 +1374,8 @@
13751375 'lockmanager-fail-releaselock',
13761376 'lockmanager-fail-acquirelocks',
13771377 'lockmanager-fail-db-bucket',
1378 - 'lockmanager-fail-db-release'
 1378+ 'lockmanager-fail-db-release',
 1379+ 'lockmanager-fail-svr-release'
13791380 ),
13801381
13811382 'zip' => array(
Index: branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php
@@ -0,0 +1,414 @@
 2+<?php
 3+
 4+if ( php_sapi_name() !== 'cli' ) {
 5+ die( "This is not a valid entry point.\n" );
 6+}
 7+error_reporting( E_ALL );
 8+
 9+// Run the server...
 10+set_time_limit( 0 );
 11+LockServerDaemon::init(
 12+ getopt( '', array(
 13+ 'address:', 'port:', 'authKey:',
 14+ 'connTimeout::', 'lockTimeout::', 'maxClients::', 'maxBacklog::', 'maxLocks::',
 15+ ) )
 16+)->main();
 17+
 18+/**
 19+ * Simple lock server daemon that accepts lock/unlock requests.
 20+ * This should not require MediaWiki setup or PHP files.
 21+ */
 22+class LockServerDaemon {
 23+ /** @var resource */
 24+ protected $sock; // socket to listen/accept on
 25+ /** @var Array */
 26+ protected $shLocks = array(); // (key => session => 1)
 27+ /** @var Array */
 28+ protected $exLocks = array(); // (key => session)
 29+ /** @var Array */
 30+ protected $sessions = array(); // (session => resource)
 31+ /** @var Array */
 32+ protected $deadSessions = array(); // (session => UNIX timestamp)
 33+
 34+ /** @var Array */
 35+ protected $sessionIndexSh = array(); // (session => key => 1)
 36+ /** @var Array */
 37+ protected $sessionIndexEx = array(); // (session => key => 1)
 38+
 39+ protected $authKey; // string key
 40+ protected $connTimeout; // array ( 'sec' => integer, 'usec' => integer )
 41+ protected $lockTimeout; // integer number of seconds
 42+ protected $maxLocks; // integer
 43+ protected $maxClients; // integer
 44+
 45+ protected $startTime; // integer UNIX timestamp
 46+ protected $lockCount = 0; // integer
 47+ protected $ticks = 0; // integer counter
 48+
 49+ protected static $instance = null;
 50+
 51+ /**
 52+ * @params $config Array
 53+ * @return LockServerDaemon
 54+ */
 55+ public function init( array $config ) {
 56+ if ( self::$instance ) {
 57+ throw new Exception( 'LockServer already initialized.' );
 58+ }
 59+ self::$instance = new self( $config );
 60+ return self::$instance;
 61+ }
 62+
 63+ /**
 64+ * @params $config Array
 65+ */
 66+ protected function __construct( array $config ) {
 67+ $required = array( 'address', 'port', 'authKey' );
 68+ foreach ( $required as $par ) {
 69+ if ( !isset( $config[$par] ) ) {
 70+ throw new Exception( "Parameter '$par' must be specified." );
 71+ }
 72+ }
 73+
 74+ $this->authKey = $config['authKey'];
 75+ $connTimeout = isset( $config['connTimeout'] )
 76+ ? $config['connTimeout']
 77+ : 1.5;
 78+ $this->connTimeout = array(
 79+ 'sec' => floor( $connTimeout ),
 80+ 'usec' => floor( ( $connTimeout - floor( $connTimeout ) ) * 1e6 )
 81+ );
 82+ $this->lockTimeout = isset( $config['lockTimeout'] )
 83+ ? $config['lockTimeout']
 84+ : 60;
 85+ $this->maxLocks = isset( $config['maxLocks'] )
 86+ ? $config['maxLocks']
 87+ : 5000;
 88+ $this->maxClients = isset( $config['maxClients'] )
 89+ ? $config['maxClients']
 90+ : 100;
 91+ $backlog = isset( $config['maxBacklog'] )
 92+ ? $config['maxBacklog']
 93+ : 10;
 94+
 95+ if ( !function_exists( 'socket_create' ) ) {
 96+ throw new Exception( "PHP sockets extension missing from PHP CLI mode." );
 97+ }
 98+ $sock = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
 99+ if ( $sock === false ) {
 100+ throw new Exception( "socket_create(): " . socket_strerror( socket_last_error() ) );
 101+ }
 102+ socket_set_option( $sock, SOL_SOCKET, SO_REUSEADDR, 1 ); // bypass 2MLS
 103+ if ( socket_bind( $sock, $config['address'], $config['port'] ) === false ) {
 104+ throw new Exception( "socket_bind(): " .
 105+ socket_strerror( socket_last_error( $sock ) ) );
 106+ } elseif ( socket_listen( $sock, $backlog ) === false ) {
 107+ throw new Exception( "socket_listen(): " .
 108+ socket_strerror( socket_last_error( $sock ) ) );
 109+ }
 110+ $this->sock = $sock;
 111+
 112+ $this->startTime = time();
 113+ }
 114+
 115+ /**
 116+ * @return void
 117+ */
 118+ public function main() {
 119+ // Create a list of all the clients that will be connected to us.
 120+ $clients = array( $this->sock ); // start off with listening socket
 121+ do {
 122+ // Create a copy, so $clients doesn't get modified by socket_select()
 123+ $read = $clients; // clients-with-data
 124+ // Get a list of all the clients that have data to be read from
 125+ $changed = socket_select( $read, $write = NULL, $except = NULL, NULL );
 126+ if ( $changed === false ) {
 127+ trigger_error( 'socket_listen(): ' . socket_strerror( socket_last_error() ) );
 128+ continue;
 129+ } elseif ( $changed < 1 ) {
 130+ continue; // wait
 131+ }
 132+ // Check if there is a client trying to connect...
 133+ if ( in_array( $this->sock, $read ) && count( $clients ) < $this->maxClients ) {
 134+ // Accept the new client...
 135+ $newsock = socket_accept( $this->sock );
 136+ socket_set_option( $newsock, SOL_SOCKET, SO_RCVTIMEO, $this->connTimeout );
 137+ socket_set_option( $newsock, SOL_SOCKET, SO_SNDTIMEO, $this->connTimeout );
 138+ $clients[] = $newsock;
 139+ // Remove the listening socket from the clients-with-data array...
 140+ $key = array_search( $this->sock, $read );
 141+ unset( $read[$key] );
 142+ }
 143+ // Loop through all the clients that have data to read...
 144+ foreach ( $read as $read_sock ) {
 145+ // Read until newline or 65535 bytes are recieved.
 146+ // socket_read show errors when the client is disconnected.
 147+ $data = @socket_read( $read_sock, 65535, PHP_NORMAL_READ );
 148+ // Check if the client is disconnected
 149+ if ( $data === false ) {
 150+ // Remove client for $clients array
 151+ $key = array_search( $read_sock, $clients );
 152+ unset( $clients[$key] );
 153+ // Remove socket's session from tracking (if it exists)
 154+ $session = array_search( $read_sock, $this->sessions );
 155+ if ( $session !== false ) {
 156+ unset( $this->sessions[$session] );
 157+ // Record recently killed sessions that still have locks
 158+ if ( isset( $this->sessionIndexSh[$session] )
 159+ || isset( $this->sessionIndexEx[$session] ) )
 160+ {
 161+ $this->deadSessions[$session] = time();
 162+ }
 163+ }
 164+ } else {
 165+ // Perform the requested command...
 166+ $response = $this->doCommand( trim( $data ), $read_sock );
 167+ // Send the response to the client...
 168+ if ( socket_write( $read_sock, "$response\n" ) === false ) {
 169+ trigger_error( 'socket_write(): ' .
 170+ socket_strerror( socket_last_error( $read_sock ) ) );
 171+ }
 172+ }
 173+ }
 174+ // Prune dead locks every 10 socket events...
 175+ if ( ++$this->ticks >= 9 ) {
 176+ $this->ticks = 0;
 177+ $this->purgeExpiredLocks();
 178+ }
 179+ } while ( true );
 180+ }
 181+
 182+ /**
 183+ * @param $data string
 184+ * @param $sourceSock resource
 185+ * @return string
 186+ */
 187+ protected function doCommand( $data, $sourceSock ) {
 188+ $cmdArr = $this->getCommand( $data );
 189+ if ( is_string( $cmdArr ) ) {
 190+ return $cmdArr; // error
 191+ }
 192+ list( $function, $session, $type, $resources ) = $cmdArr;
 193+ // On first command, track the session => sock correspondence
 194+ if ( !isset( $this->sessions[$session] ) ) {
 195+ $this->sessions[$session] = $sourceSock;
 196+ }
 197+ if ( $function === 'ACQUIRE' ) {
 198+ return $this->lock( $session, $type, $resources );
 199+ } elseif ( $function === 'RELEASE' ) {
 200+ return $this->unlock( $session, $type, $resources );
 201+ } elseif ( $function === 'RELEASE_ALL' ) {
 202+ return $this->release( $session );
 203+ } elseif ( $function === 'STAT' ) {
 204+ return $this->stat();
 205+ }
 206+ return 'INTERNAL_ERROR';
 207+ }
 208+
 209+ /**
 210+ * @param $data string
 211+ * @return Array
 212+ */
 213+ protected function getCommand( $data ) {
 214+ $m = explode( ':', $data ); // <session, key, command, type, values>
 215+ if ( count( $m ) == 5 ) {
 216+ list( $session, $key, $command, $type, $values ) = $m;
 217+ if ( sha1( $session . $command . $type . $values . $this->authKey ) !== $key ) {
 218+ return 'BAD_KEY';
 219+ } elseif ( strlen( $session ) !== 40 ) {
 220+ return 'BAD_SESSION';
 221+ }
 222+ $values = explode( '|', $values );
 223+ if ( $command === 'ACQUIRE' ) {
 224+ $needsLockArgs = true;
 225+ } elseif ( $command === 'RELEASE' ) {
 226+ $needsLockArgs = true;
 227+ } elseif ( $command === 'RELEASE_ALL' ) {
 228+ $needsLockArgs = false;
 229+ } elseif ( $command === 'STAT' ) {
 230+ $needsLockArgs = false;
 231+ } else {
 232+ return 'BAD_COMMAND';
 233+ }
 234+ if ( $needsLockArgs ) {
 235+ if ( $type !== 'SH' && $type !== 'EX' ) {
 236+ return 'BAD_TYPE';
 237+ }
 238+ foreach ( $values as $value ) {
 239+ if ( strlen( $value ) !== 40 ) {
 240+ return 'BAD_FORMAT';
 241+ }
 242+ }
 243+ }
 244+ return array( $command, $session, $type, $values );
 245+ }
 246+ return 'BAD_FORMAT';
 247+ }
 248+
 249+ /**
 250+ * @param $session string
 251+ * @param $type string
 252+ * @param $keys Array
 253+ * @return string
 254+ */
 255+ protected function lock( $session, $type, $keys ) {
 256+ if ( $this->lockCount >= $this->maxLocks ) {
 257+ return 'TOO_MANY_LOCKS';
 258+ }
 259+ if ( $type === 'SH' ) {
 260+ // Check if any keys are already write-locked...
 261+ foreach ( $keys as $key ) {
 262+ if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] !== $session ) {
 263+ return 'CANT_ACQUIRE';
 264+ }
 265+ }
 266+ // Acquire the read-locks...
 267+ foreach ( $keys as $key ) {
 268+ $this->set_sh_lock( $key, $session );
 269+ }
 270+ return 'ACQUIRED';
 271+ } elseif ( $type === 'EX' ) {
 272+ // Check if any keys are already read-locked or write-locked...
 273+ foreach ( $keys as $key ) {
 274+ if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] !== $session ) {
 275+ return 'CANT_ACQUIRE';
 276+ }
 277+ if ( isset( $this->shLocks[$key] ) ) {
 278+ foreach ( $this->shLocks[$key] as $otherSession => $x ) {
 279+ if ( $otherSession !== $session ) {
 280+ return 'CANT_ACQUIRE';
 281+ }
 282+ }
 283+ }
 284+ }
 285+ // Acquire the write-locks...
 286+ foreach ( $keys as $key ) {
 287+ $this->set_ex_lock( $key, $session );
 288+ }
 289+ return 'ACQUIRED';
 290+ }
 291+ return 'INTERNAL_ERROR';
 292+ }
 293+
 294+ /**
 295+ * @param $session string
 296+ * @param $type string
 297+ * @param $keys Array
 298+ * @return string
 299+ */
 300+ protected function unlock( $session, $type, $keys ) {
 301+ if ( $type === 'SH' ) {
 302+ foreach ( $keys as $key ) {
 303+ $this->unset_sh_lock( $key, $session );
 304+ }
 305+ return 'RELEASED';
 306+ } elseif ( $type === 'EX' ) {
 307+ foreach ( $keys as $key ) {
 308+ $this->unset_ex_lock( $key, $session );
 309+ }
 310+ return 'RELEASED';
 311+ }
 312+ return 'INTERNAL_ERROR';
 313+ }
 314+
 315+ /**
 316+ * @param $session string
 317+ * @return string
 318+ */
 319+ protected function release( $session ) {
 320+ if ( isset( $this->sessionIndexSh[$session] ) ) {
 321+ foreach ( $this->sessionIndexSh[$session] as $key => $x ) {
 322+ $this->unset_sh_lock( $key, $session );
 323+ }
 324+ }
 325+ if ( isset( $this->sessionIndexEx[$session] ) ) {
 326+ foreach ( $this->sessionIndexEx[$session] as $key => $x ) {
 327+ $this->unset_ex_lock( $key, $session );
 328+ }
 329+ }
 330+ return 'RELEASED_ALL';
 331+ }
 332+
 333+ /**
 334+ * @return string
 335+ */
 336+ protected function stat() {
 337+ return ( time() - $this->startTime ) . ':' . memory_get_usage();
 338+ }
 339+
 340+ /**
 341+ * Clear locks for sessions that have been dead for a while
 342+ *
 343+ * @return void
 344+ */
 345+ protected function purgeExpiredLocks() {
 346+ $now = time();
 347+ foreach ( $this->deadSessions as $session => $timestamp ) {
 348+ if ( ( $now - $timestamp ) > $this->lockTimeout ) {
 349+ $this->release( $session );
 350+ unset( $this->deadSessions[$session] );
 351+ }
 352+ }
 353+ }
 354+
 355+ /**
 356+ * @param $key string
 357+ * @param $session string
 358+ * @return void
 359+ */
 360+ protected function set_sh_lock( $key, $session ) {
 361+ if ( !isset( $this->shLocks[$key][$session] ) ) {
 362+ $this->shLocks[$key][$session] = 1;
 363+ $this->sessionIndexSh[$session][$key] = 1;
 364+ ++$this->lockCount; // we are adding a lock
 365+ }
 366+ }
 367+
 368+ /**
 369+ * @param $key string
 370+ * @param $session string
 371+ * @return void
 372+ */
 373+ protected function set_ex_lock( $key, $session ) {
 374+ if ( !isset( $this->exLocks[$key][$session] ) ) {
 375+ $this->exLocks[$key] = $session;
 376+ $this->sessionIndexEx[$session][$key] = 1;
 377+ ++$this->lockCount; // we are adding a lock
 378+ }
 379+ }
 380+
 381+ /**
 382+ * @param $key string
 383+ * @param $session string
 384+ * @return void
 385+ */
 386+ protected function unset_sh_lock( $key, $session ) {
 387+ if ( isset( $this->shLocks[$key][$session] ) ) {
 388+ unset( $this->shLocks[$key][$session] );
 389+ if ( !count( $this->shLocks[$key] ) ) {
 390+ unset( $this->shLocks[$key] );
 391+ }
 392+ unset( $this->sessionIndexSh[$session][$key] );
 393+ if ( !count( $this->sessionIndexSh[$session] ) ) {
 394+ unset( $this->sessionIndexSh[$session] );
 395+ }
 396+ --$this->lockCount;
 397+ }
 398+ }
 399+
 400+ /**
 401+ * @param $key string
 402+ * @param $session string
 403+ * @return void
 404+ */
 405+ protected function unset_ex_lock( $key, $session ) {
 406+ if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] === $session ) {
 407+ unset( $this->exLocks[$key] );
 408+ unset( $this->sessionIndexEx[$session][$key] );
 409+ if ( !count( $this->sessionIndexEx[$session] ) ) {
 410+ unset( $this->sessionIndexEx[$session] );
 411+ }
 412+ --$this->lockCount;
 413+ }
 414+ }
 415+}
Property changes on: branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php
___________________________________________________________________
Added: svn:eol-style
1416 + native
Index: branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
@@ -0,0 +1,285 @@
 2+<?php
 3+
 4+/**
 5+ * Version of LockManager based on using lock daemon servers.
 6+ * This is meant for multi-wiki systems that may share files.
 7+ * All locks are non-blocking, which avoids deadlocks.
 8+ *
 9+ * All lock requests for a resource, identified by a hash string, will map
 10+ * to one bucket. Each bucket maps to one or several peer servers, each
 11+ * running LockServerDaemon.php, listening on a designated TCP port.
 12+ * A majority of peers must agree for a lock to be acquired.
 13+ */
 14+class LSLockManager extends LockManager {
 15+ /** @var Array Mapping of lock types to the type actually used */
 16+ protected $lockTypeMap = array(
 17+ self::LOCK_SH => self::LOCK_SH,
 18+ self::LOCK_UW => self::LOCK_SH,
 19+ self::LOCK_EX => self::LOCK_EX
 20+ );
 21+
 22+ /** @var Array Map of server names to server config */
 23+ protected $lockServers; // (server name => server config array)
 24+ /** @var Array Map of bucket indexes to peer server lists */
 25+ protected $srvsByBucket; // (bucket index => (lsrv1, lsrv2, ...))
 26+
 27+ /** @var Array Map of (locked key => lock type => count) */
 28+ protected $locksHeld = array();
 29+ /** @var Array Map Server connections (server name => resource) */
 30+ protected $conns = array();
 31+
 32+ protected $connTimeout; // float number of seconds
 33+ protected $session = ''; // random SHA-1 string
 34+
 35+ /**
 36+ * Construct a new instance from configuration.
 37+ * $config paramaters include:
 38+ * 'lockServers' : Associative array of server names to configuration.
 39+ * Configuration is an associative array that includes:
 40+ * 'host' - IP address/hostname
 41+ * 'port' - TCP port
 42+ * 'authKey' - Secret string the lock server uses
 43+ * 'srvsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
 44+ * each having an odd-numbered list of server names (peers) as values.
 45+ * 'connTimeout' : Lock server connection attempt timeout. [optional]
 46+ *
 47+ * @param Array $config
 48+ */
 49+ public function __construct( array $config ) {
 50+ $this->lockServers = $config['lockServers'];
 51+ // Sanitize srvsByBucket config to prevent PHP errors
 52+ $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
 53+ $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
 54+
 55+ if ( isset( $config['connTimeout'] ) ) {
 56+ $this->connTimeout = $config['connTimeout'];
 57+ } else {
 58+ $this->connTimeout = 3; // use some sane amount
 59+ }
 60+
 61+ $this->session = '';
 62+ for ( $i = 0; $i < 5; $i++ ) {
 63+ $this->session .= mt_rand( 0, 2147483647 );
 64+ }
 65+ $this->session = sha1( $this->session );
 66+ }
 67+
 68+ protected function doLock( array $keys, $type ) {
 69+ $status = Status::newGood();
 70+
 71+ $keysToLock = array();
 72+ // Get locks that need to be acquired (buckets => locks)...
 73+ foreach ( $keys as $key ) {
 74+ if ( isset( $this->locksHeld[$key][$type] ) ) {
 75+ ++$this->locksHeld[$key][$type];
 76+ } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] ) ) {
 77+ $this->locksHeld[$key][$type] = 1;
 78+ } else {
 79+ $bucket = $this->getBucketFromKey( $key );
 80+ $keysToLock[$bucket][] = $key;
 81+ }
 82+ }
 83+
 84+ $lockedKeys = array(); // files locked in this attempt
 85+ // Attempt to acquire these locks...
 86+ foreach ( $keysToLock as $bucket => $keys ) {
 87+ // Try to acquire the locks for this bucket
 88+ $res = $this->doLockingRequestAll( $bucket, $keys, $type );
 89+ if ( $res === 'cantacquire' ) {
 90+ // Resources already locked by another process.
 91+ // Abort and unlock everything we just locked.
 92+ $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
 93+ $status->merge( $this->doUnlock( $lockedKeys, $type ) );
 94+ return $status;
 95+ } elseif ( $res !== true ) {
 96+ // Couldn't contact any servers for this bucket.
 97+ // Abort and unlock everything we just locked.
 98+ $status->fatal( 'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
 99+ $status->merge( $this->doUnlock( $lockedKeys, $type ) );
 100+ return $status;
 101+ }
 102+ // Record these locks as active
 103+ foreach ( $keys as $key ) {
 104+ $this->locksHeld[$key][$type] = 1; // locked
 105+ }
 106+ // Keep track of what locks were made in this attempt
 107+ $lockedKeys = array_merge( $lockedKeys, $keys );
 108+ }
 109+
 110+ return $status;
 111+ }
 112+
 113+ protected function doUnlock( array $keys, $type ) {
 114+ $status = Status::newGood();
 115+
 116+ foreach ( $keys as $key ) {
 117+ if ( !isset( $this->locksHeld[$key] ) ) {
 118+ $status->warning( 'lockmanager-notlocked', $key );
 119+ } elseif ( !isset( $this->locksHeld[$key][$type] ) ) {
 120+ $status->warning( 'lockmanager-notlocked', $key );
 121+ } else {
 122+ --$this->locksHeld[$key][$type];
 123+ if ( $this->locksHeld[$key][$type] <= 0 ) {
 124+ unset( $this->locksHeld[$key][$type] );
 125+ }
 126+ if ( !count( $this->locksHeld[$key] ) ) {
 127+ unset( $this->locksHeld[$key] ); // no SH or EX locks left for key
 128+ }
 129+ }
 130+ }
 131+
 132+ // Reference count the locks held and release locks when zero
 133+ if ( !count( $this->locksHeld ) ) {
 134+ $status->merge( $this->releaseLocks() );
 135+ }
 136+
 137+ return $status;
 138+ }
 139+
 140+ /**
 141+ * Get a connection to a lock server and acquire locks on $keys.
 142+ *
 143+ * @param $lockSrv string
 144+ * @param $keys Array
 145+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
 146+ * @return bool Resources able to be locked
 147+ */
 148+ protected function doLockingRequest( $lockSrv, array $keys, $type ) {
 149+ if ( $type == self::LOCK_SH ) { // reader locks
 150+ $type = 'SH';
 151+ } elseif ( $type == self::LOCK_EX ) { // writer locks
 152+ $type = 'EX';
 153+ } else {
 154+ return true; // ok...
 155+ }
 156+
 157+ // Send out the command and get the response...
 158+ $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
 159+
 160+ return ( $response === 'ACQUIRED' );
 161+ }
 162+
 163+ /**
 164+ * Send a command and get back the response
 165+ *
 166+ * @param $lockSrv string
 167+ * @param $action string
 168+ * @param $type string
 169+ * @param $values Array
 170+ * @return string|false
 171+ */
 172+ protected function sendCommand( $lockSrv, $action, $type, $values ) {
 173+ $conn = $this->getConnection( $lockSrv );
 174+ if ( !$conn ) {
 175+ return false; // no connection
 176+ }
 177+ $authKey = $this->lockServers[$lockSrv]['authKey'];
 178+ // Build of the command as a flat string...
 179+ $values = implode( '|', $values );
 180+ $key = sha1( $this->session . $action . $type . $values . $authKey );
 181+ // Send out the command...
 182+ if ( fwrite( $conn, "{$this->session}:$key:$action:$type:$values\n" ) === false ) {
 183+ return false;
 184+ }
 185+ // Get the response...
 186+ $response = fgets( $conn );
 187+ if ( $response === false ) {
 188+ return false;
 189+ }
 190+ return trim( $response );
 191+ }
 192+
 193+ /**
 194+ * Attempt to acquire locks with the peers for a bucket.
 195+ *
 196+ * @param $bucket integer
 197+ * @param $keys Array List of resource keys to lock
 198+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
 199+ * @return bool|string One of (true, 'cantacquire', 'srverrors')
 200+ */
 201+ protected function doLockingRequestAll( $bucket, array $keys, $type ) {
 202+ $yesVotes = 0; // locks made on trustable servers
 203+ $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
 204+ $quorum = floor( $votesLeft/2 + 1 ); // simple majority
 205+ // Get votes for each peer, in order, until we have enough...
 206+ foreach ( $this->srvsByBucket[$bucket] as $index => $lockSrv ) {
 207+ // Attempt to acquire the lock on this peer
 208+ if ( !$this->doLockingRequest( $lockSrv, $keys, $type ) ) {
 209+ return 'cantacquire'; // vetoed; resource locked
 210+ }
 211+ ++$yesVotes; // success for this peer
 212+ if ( $yesVotes >= $quorum ) {
 213+ return true; // lock obtained
 214+ }
 215+ $votesLeft--;
 216+ $votesNeeded = $quorum - $yesVotes;
 217+ if ( $votesNeeded > $votesLeft ) {
 218+ // In "trust cache" mode we don't have to meet the quorum
 219+ break; // short-circuit
 220+ }
 221+ }
 222+ // At this point, we must not have meet the quorum
 223+ return 'srverrors'; // not enough votes to ensure correctness
 224+ }
 225+
 226+ /**
 227+ * Get (or reuse) a connection to a lock server
 228+ *
 229+ * @param $lockSrv string
 230+ * @return resource
 231+ */
 232+ protected function getConnection( $lockSrv ) {
 233+ if ( !isset( $this->conns[$lockSrv] ) ) {
 234+ $cfg = $this->lockServers[$lockSrv];
 235+ wfSuppressWarnings();
 236+ $errno = $errstr = '';
 237+ $conn = fsockopen( $cfg['host'], $cfg['port'], $errno, $errstr, $this->connTimeout );
 238+ wfRestoreWarnings();
 239+ if ( $conn === false ) {
 240+ return null;
 241+ }
 242+ $sec = floor( $this->connTimeout );
 243+ $usec = floor( ( $this->connTimeout - floor( $this->connTimeout ) ) * 1e6 );
 244+ stream_set_timeout( $conn, $sec, $usec );
 245+ $this->conns[$lockSrv] = $conn;
 246+ }
 247+ return $this->conns[$lockSrv];
 248+ }
 249+
 250+ /**
 251+ * Release all locks that this session is holding.
 252+ *
 253+ * @return Status
 254+ */
 255+ protected function releaseLocks() {
 256+ $status = Status::newGood();
 257+ foreach ( $this->conns as $lockSrv => $conn ) {
 258+ $response = $this->sendCommand( $lockSrv, 'RELEASE_ALL', '', array() );
 259+ if ( $response !== 'RELEASED_ALL' ) {
 260+ $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
 261+ }
 262+ }
 263+ return $status;
 264+ }
 265+
 266+ /**
 267+ * Get the bucket for lock key.
 268+ *
 269+ * @param $key string (40 char hex key)
 270+ * @return integer
 271+ */
 272+ protected function getBucketFromKey( $key ) {
 273+ $prefix = substr( $key, 0, 2 ); // first 2 hex chars (8 bits)
 274+ return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->srvsByBucket );
 275+ }
 276+
 277+ /**
 278+ * Make sure remaining locks get cleared for sanity
 279+ */
 280+ function __destruct() {
 281+ $this->releaseLocks();
 282+ foreach ( $this->conns as $lockSrv => $conn ) {
 283+ fclose( $conn );
 284+ }
 285+ }
 286+}
Property changes on: branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
___________________________________________________________________
Added: svn:eol-style
1287 + native
Index: branches/FileBackend/phase3/includes/AutoLoader.php
@@ -496,6 +496,7 @@
497497 'ScopedLock' => 'includes/filerepo/backend/lockmanager/LockManager.php',
498498 'FSLockManager' => 'includes/filerepo/backend/lockmanager/FSLockManager.php',
499499 'DBLockManager' => 'includes/filerepo/backend/lockmanager/DBLockManager.php',
 500+ 'LSLockManager' => 'includes/filerepo/backend/lockmanager/LSLockManager.php',
500501 'MySqlLockManager'=> 'includes/filerepo/backend/lockmanager/DBLockManager.php',
501502 'NullLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
502503 'FileOp' => 'includes/filerepo/backend/FileOp.php',
Index: branches/FileBackend/phase3/languages/messages/MessagesEn.php
@@ -2279,8 +2279,9 @@
22802280 'lockmanager-fail-acquirelock' => 'Could not acquire lock for key "$1".',
22812281 'lockmanager-fail-releaselock' => 'Could not release lock for key "$1".',
22822282 'lockmanager-fail-acquirelocks' => 'Could not acquire locks for keys "$1".',
2283 -'lockmanager-fail-db-bucket' => 'Could not contact enough lock servers in bucket $1',
2284 -'lockmanager-fail-db-release' => 'Could not release locks on server $1',
 2283+'lockmanager-fail-db-bucket' => 'Could not contact enough lock databases in bucket $1',
 2284+'lockmanager-fail-db-release' => 'Could not release locks on database $1',
 2285+'lockmanager-fail-svr-release' => 'Could not release locks on server $1',
22852286
22862287 # img_auth script messages
22872288 'img-auth-accessdenied' => 'Access denied',

Follow-up revisions

RevisionCommit summaryAuthorDate
r107331Followup r106554...reedy17:06, 26 December 2011

Status & tagging log