https://www.mediawiki.org/wiki/Special:Code/MediaWiki/106554

Revision: 106554
Author:   aaron
Date:     2011-12-18 08:16:17 +0000 (Sun, 18 Dec 2011)
Log Message:
-----------
* Added LSLockManager and and corresponding LockServerDaemon.php file
* Message tweaks

Modified Paths:
--------------
    branches/FileBackend/phase3/includes/AutoLoader.php
    branches/FileBackend/phase3/languages/messages/MessagesEn.php
    branches/FileBackend/phase3/maintenance/language/messages.inc

Added Paths:
-----------
    
branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
    branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php

Modified: branches/FileBackend/phase3/includes/AutoLoader.php
===================================================================
--- branches/FileBackend/phase3/includes/AutoLoader.php 2011-12-18 03:33:31 UTC 
(rev 106553)
+++ branches/FileBackend/phase3/includes/AutoLoader.php 2011-12-18 08:16:17 UTC 
(rev 106554)
@@ -496,6 +496,7 @@
        'ScopedLock' => 'includes/filerepo/backend/lockmanager/LockManager.php',
        'FSLockManager' => 
'includes/filerepo/backend/lockmanager/FSLockManager.php',
        'DBLockManager' => 
'includes/filerepo/backend/lockmanager/DBLockManager.php',
+       'LSLockManager' => 
'includes/filerepo/backend/lockmanager/LSLockManager.php',
        'MySqlLockManager'=> 
'includes/filerepo/backend/lockmanager/DBLockManager.php',
        'NullLockManager' => 
'includes/filerepo/backend/lockmanager/LockManager.php',
        'FileOp' => 'includes/filerepo/backend/FileOp.php',

Added: 
branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
===================================================================
--- 
branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
                         (rev 0)
+++ 
branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
 2011-12-18 08:16:17 UTC (rev 106554)
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * Version of LockManager based on using lock daemon servers.
+ * This is meant for multi-wiki systems that may share files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * All lock requests for a resource, identified by a hash string, will map
+ * to one bucket. Each bucket maps to one or several peer servers, each
+ * running LockServerDaemon.php, listening on a designated TCP port.
+ * A majority of peers must agree for a lock to be acquired.
+ */
+class LSLockManager extends LockManager {
+       /** @var Array Mapping of lock types to the type actually used */
+       protected $lockTypeMap = array(
+               self::LOCK_SH => self::LOCK_SH,
+               self::LOCK_UW => self::LOCK_SH,
+               self::LOCK_EX => self::LOCK_EX
+       );
+
+       /** @var Array Map of server names to server config */
+       protected $lockServers; // (server name => server config array)
+       /** @var Array Map of bucket indexes to peer server lists */
+       protected $srvsByBucket; // (bucket index => (lsrv1, lsrv2, ...))
+
+       /** @var Array Map of (locked key => lock type => count) */
+       protected $locksHeld = array();
+       /** @var Array Map Server connections (server name => resource) */
+       protected $conns = array();
+
+       protected $connTimeout; // float number of seconds
+       protected $session = ''; // random SHA-1 string
+
+       /**
+        * Construct a new instance from configuration.
+        * $config paramaters include:
+        *     'lockServers'  : Associative array of server names to 
configuration.
+        *                      Configuration is an associative array that 
includes:
+        *                      'host'    - IP address/hostname
+        *                      'port'    - TCP port
+        *                      'authKey' - Secret string the lock server uses
+        *     'srvsByBucket' : Array of 1-16 consecutive integer keys, 
starting from 0,
+        *                      each having an odd-numbered list of server 
names (peers) as values.
+        *     'connTimeout'  : Lock server connection attempt timeout. 
[optional]
+        *
+        * @param Array $config 
+        */
+       public function __construct( array $config ) {
+               $this->lockServers = $config['lockServers'];
+               // Sanitize srvsByBucket config to prevent PHP errors
+               $this->srvsByBucket = array_filter( $config['srvsByBucket'], 
'is_array' );
+               $this->srvsByBucket = array_values( $this->srvsByBucket ); // 
consecutive
+
+               if ( isset( $config['connTimeout'] ) ) {
+                       $this->connTimeout = $config['connTimeout'];
+               } else {
+                       $this->connTimeout = 3; // use some sane amount
+               }
+
+               $this->session = '';
+               for ( $i = 0; $i < 5; $i++ ) {
+                       $this->session .= mt_rand( 0, 2147483647 );
+               }
+               $this->session = sha1( $this->session );
+       }
+
+       protected function doLock( array $keys, $type ) {
+               $status = Status::newGood();
+
+               $keysToLock = array();
+               // Get locks that need to be acquired (buckets => locks)...
+               foreach ( $keys as $key ) {
+                       if ( isset( $this->locksHeld[$key][$type] ) ) {
+                               ++$this->locksHeld[$key][$type];
+                       } elseif ( isset( $this->locksHeld[$key][self::LOCK_EX] 
) ) {
+                               $this->locksHeld[$key][$type] = 1;
+                       } else {
+                               $bucket = $this->getBucketFromKey( $key );
+                               $keysToLock[$bucket][] = $key;
+                       }
+               }
+
+               $lockedKeys = array(); // files locked in this attempt
+               // Attempt to acquire these locks...
+               foreach ( $keysToLock as $bucket => $keys ) {
+                       // Try to acquire the locks for this bucket
+                       $res = $this->doLockingRequestAll( $bucket, $keys, 
$type );
+                       if ( $res === 'cantacquire' ) {
+                               // Resources already locked by another process.
+                               // Abort and unlock everything we just locked.
+                               $status->fatal( 
'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
+                               $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
+                               return $status;
+                       } elseif ( $res !== true ) {
+                               // Couldn't contact any servers for this bucket.
+                               // Abort and unlock everything we just locked.
+                               $status->fatal( 
'lockmanager-fail-acquirelocks', implode( ', ', $keys ) );
+                               $status->merge( $this->doUnlock( $lockedKeys, 
$type ) );
+                               return $status;
+                       }
+                       // Record these locks as active
+                       foreach ( $keys as $key ) {
+                               $this->locksHeld[$key][$type] = 1; // locked
+                       }
+                       // Keep track of what locks were made in this attempt
+                       $lockedKeys = array_merge( $lockedKeys, $keys );
+               }
+
+               return $status;
+       }
+
+       protected function doUnlock( array $keys, $type ) {
+               $status = Status::newGood();
+
+               foreach ( $keys as $key ) {
+                       if ( !isset( $this->locksHeld[$key] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $key 
);
+                       } elseif ( !isset( $this->locksHeld[$key][$type] ) ) {
+                               $status->warning( 'lockmanager-notlocked', $key 
);
+                       } else {
+                               --$this->locksHeld[$key][$type];
+                               if ( $this->locksHeld[$key][$type] <= 0 ) {
+                                       unset( $this->locksHeld[$key][$type] );
+                               }
+                               if ( !count( $this->locksHeld[$key] ) ) {
+                                       unset( $this->locksHeld[$key] ); // no 
SH or EX locks left for key
+                               }
+                       }
+               }
+
+               // Reference count the locks held and release locks when zero
+               if ( !count( $this->locksHeld ) ) {
+                       $status->merge( $this->releaseLocks() );
+               }
+
+               return $status;
+       }
+
+       /**
+        * Get a connection to a lock server and acquire locks on $keys.
+        *
+        * @param $lockSrv string
+        * @param $keys Array
+        * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+        * @return bool Resources able to be locked
+        */
+       protected function doLockingRequest( $lockSrv, array $keys, $type ) {
+               if ( $type == self::LOCK_SH ) { // reader locks
+                       $type = 'SH';
+               } elseif ( $type == self::LOCK_EX ) { // writer locks
+                       $type = 'EX';
+               } else {
+                       return true; // ok...
+               }
+
+               // Send out the command and get the response...
+               $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, 
$keys );
+
+               return ( $response === 'ACQUIRED' );
+       }
+
+       /**
+        * Send a command and get back the response
+        *
+        * @param $lockSrv string
+        * @param $action string
+        * @param $type string
+        * @param $values Array
+        * @return string|false
+        */
+       protected function sendCommand( $lockSrv, $action, $type, $values ) {
+               $conn = $this->getConnection( $lockSrv );
+               if ( !$conn ) {
+                       return false; // no connection
+               }
+               $authKey = $this->lockServers[$lockSrv]['authKey'];
+               // Build of the command as a flat string...
+               $values = implode( '|', $values );
+               $key = sha1( $this->session . $action . $type . $values . 
$authKey );
+               // Send out the command...
+               if ( fwrite( $conn, 
"{$this->session}:$key:$action:$type:$values\n" ) === false ) {
+                       return false;
+               }
+               // Get the response...
+               $response = fgets( $conn );
+               if ( $response === false ) {
+                       return false;
+               }
+               return trim( $response );
+       }
+
+       /**
+        * Attempt to acquire locks with the peers for a bucket.
+        *
+        * @param $bucket integer
+        * @param $keys Array List of resource keys to lock
+        * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+        * @return bool|string One of (true, 'cantacquire', 'srverrors')
+        */
+       protected function doLockingRequestAll( $bucket, array $keys, $type ) {
+               $yesVotes = 0; // locks made on trustable servers
+               $votesLeft = count( $this->srvsByBucket[$bucket] ); // 
remaining peers
+               $quorum = floor( $votesLeft/2 + 1 ); // simple majority
+               // Get votes for each peer, in order, until we have enough...
+               foreach ( $this->srvsByBucket[$bucket] as $index => $lockSrv ) {
+                       // Attempt to acquire the lock on this peer
+                       if ( !$this->doLockingRequest( $lockSrv, $keys, $type ) 
) {
+                               return 'cantacquire'; // vetoed; resource locked
+                       }
+                       ++$yesVotes; // success for this peer
+                       if ( $yesVotes >= $quorum ) {
+                               return true; // lock obtained
+                       }
+                       $votesLeft--;
+                       $votesNeeded = $quorum - $yesVotes;
+                       if ( $votesNeeded > $votesLeft ) {
+                               // In "trust cache" mode we don't have to meet 
the quorum
+                               break; // short-circuit
+                       }
+               }
+               // At this point, we must not have meet the quorum
+               return 'srverrors'; // not enough votes to ensure correctness
+       }
+
+       /**
+        * Get (or reuse) a connection to a lock server
+        *
+        * @param $lockSrv string
+        * @return resource
+        */
+       protected function getConnection( $lockSrv ) {
+               if ( !isset( $this->conns[$lockSrv] ) ) {
+                       $cfg = $this->lockServers[$lockSrv];
+                       wfSuppressWarnings();
+                       $errno = $errstr = '';
+                       $conn = fsockopen( $cfg['host'], $cfg['port'], $errno, 
$errstr, $this->connTimeout );
+                       wfRestoreWarnings();
+                       if ( $conn === false ) {
+                               return null;
+                       }
+                       $sec = floor( $this->connTimeout );
+                       $usec = floor( ( $this->connTimeout - floor( 
$this->connTimeout ) ) * 1e6 );
+                       stream_set_timeout( $conn, $sec, $usec );
+                       $this->conns[$lockSrv] = $conn;
+               }
+               return $this->conns[$lockSrv];
+       }
+
+       /**
+        * Release all locks that this session is holding.
+        *
+        * @return Status
+        */
+       protected function releaseLocks() {
+               $status = Status::newGood();
+               foreach ( $this->conns as $lockSrv => $conn ) {
+                       $response = $this->sendCommand( $lockSrv, 
'RELEASE_ALL', '', array() );
+                       if ( $response !== 'RELEASED_ALL' ) {
+                               $status->fatal( 'lockmanager-fail-svr-release', 
$lockSrv );
+                       }
+               }
+               return $status;
+       }
+
+       /**
+        * Get the bucket for lock key.
+        *
+        * @param $key string (40 char hex key)
+        * @return integer
+        */
+       protected function getBucketFromKey( $key ) {
+               $prefix = substr( $key, 0, 2 ); // first 2 hex chars (8 bits)
+               return intval( base_convert( $prefix, 16, 10 ) ) % count( 
$this->srvsByBucket );
+       }
+
+       /**
+        * Make sure remaining locks get cleared for sanity
+        */
+       function __destruct() {
+               $this->releaseLocks();
+               foreach ( $this->conns as $lockSrv => $conn ) {
+                       fclose( $conn );
+               }
+       }
+}


Property changes on: 
branches/FileBackend/phase3/includes/filerepo/backend/lockmanager/LSLockManager.php
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: branches/FileBackend/phase3/languages/messages/MessagesEn.php
===================================================================
--- branches/FileBackend/phase3/languages/messages/MessagesEn.php       
2011-12-18 03:33:31 UTC (rev 106553)
+++ branches/FileBackend/phase3/languages/messages/MessagesEn.php       
2011-12-18 08:16:17 UTC (rev 106554)
@@ -2279,8 +2279,9 @@
 'lockmanager-fail-acquirelock'  => 'Could not acquire lock for key "$1".',
 'lockmanager-fail-releaselock'  => 'Could not release lock for key "$1".',
 'lockmanager-fail-acquirelocks' => 'Could not acquire locks for keys "$1".',
-'lockmanager-fail-db-bucket'    => 'Could not contact enough lock servers in 
bucket $1',
-'lockmanager-fail-db-release'   => 'Could not release locks on server $1',
+'lockmanager-fail-db-bucket'    => 'Could not contact enough lock databases in 
bucket $1',
+'lockmanager-fail-db-release'   => 'Could not release locks on database $1',
+'lockmanager-fail-svr-release'  => 'Could not release locks on server $1',
 
 # img_auth script messages
 'img-auth-accessdenied'     => 'Access denied',

Modified: branches/FileBackend/phase3/maintenance/language/messages.inc
===================================================================
--- branches/FileBackend/phase3/maintenance/language/messages.inc       
2011-12-18 03:33:31 UTC (rev 106553)
+++ branches/FileBackend/phase3/maintenance/language/messages.inc       
2011-12-18 08:16:17 UTC (rev 106554)
@@ -1374,7 +1374,8 @@
                'lockmanager-fail-releaselock',
                'lockmanager-fail-acquirelocks',
                'lockmanager-fail-db-bucket',
-               'lockmanager-fail-db-release'
+               'lockmanager-fail-db-release',
+               'lockmanager-fail-svr-release'
        ),
 
        'zip' => array(

Added: branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php
===================================================================
--- branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php        
                        (rev 0)
+++ branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php        
2011-12-18 08:16:17 UTC (rev 106554)
@@ -0,0 +1,414 @@
+<?php
+
+if ( php_sapi_name() !== 'cli' ) {
+       die( "This is not a valid entry point.\n" );
+}
+error_reporting( E_ALL );
+
+// Run the server...
+set_time_limit( 0 );
+LockServerDaemon::init(
+       getopt( '', array(
+               'address:', 'port:', 'authKey:',
+               'connTimeout::', 'lockTimeout::', 'maxClients::', 
'maxBacklog::', 'maxLocks::',
+       ) )
+)->main();
+
+/**
+ * Simple lock server daemon that accepts lock/unlock requests.
+ * This should not require MediaWiki setup or PHP files.
+ */
+class LockServerDaemon {
+       /** @var resource */
+       protected $sock; // socket to listen/accept on
+       /** @var Array */
+       protected $shLocks = array(); // (key => session => 1)
+       /** @var Array */
+       protected $exLocks = array(); // (key => session)
+       /** @var Array */
+       protected $sessions = array(); // (session => resource)
+       /** @var Array */
+       protected $deadSessions = array(); // (session => UNIX timestamp)
+
+       /** @var Array */
+       protected $sessionIndexSh = array(); // (session => key => 1)
+       /** @var Array */
+       protected $sessionIndexEx = array(); // (session => key => 1)
+
+       protected $authKey; // string key
+       protected $connTimeout; // array ( 'sec' => integer, 'usec' => integer )
+       protected $lockTimeout; // integer number of seconds
+       protected $maxLocks; // integer
+       protected $maxClients; // integer
+
+       protected $startTime; // integer UNIX timestamp
+       protected $lockCount = 0; // integer
+       protected $ticks = 0; // integer counter
+
+       protected static $instance = null;
+
+       /**
+        * @params $config Array
+        * @return LockServerDaemon
+        */
+       public function init( array $config ) {
+               if ( self::$instance ) {
+                       throw new Exception( 'LockServer already initialized.' 
);
+               }
+               self::$instance = new self( $config );
+               return self::$instance;
+       }
+
+       /**
+        * @params $config Array
+        */
+       protected function __construct( array $config ) {
+               $required = array( 'address', 'port', 'authKey' );
+               foreach ( $required as $par ) {
+                       if ( !isset( $config[$par] ) ) {
+                               throw new Exception( "Parameter '$par' must be 
specified." );
+                       } 
+               }
+
+               $this->authKey = $config['authKey'];
+               $connTimeout = isset( $config['connTimeout'] )
+                       ? $config['connTimeout']
+                       : 1.5;
+               $this->connTimeout = array(
+                       'sec'  => floor( $connTimeout ),
+                       'usec' => floor( ( $connTimeout - floor( $connTimeout ) 
) * 1e6 )
+               );
+               $this->lockTimeout = isset( $config['lockTimeout'] )
+                       ? $config['lockTimeout']
+                       : 60;
+               $this->maxLocks = isset( $config['maxLocks'] )
+                       ? $config['maxLocks']
+                       : 5000;
+               $this->maxClients = isset( $config['maxClients'] )
+                       ? $config['maxClients']
+                       : 100;
+               $backlog = isset( $config['maxBacklog'] )
+                       ? $config['maxBacklog']
+                       : 10;
+
+               if ( !function_exists( 'socket_create' ) ) {
+                       throw new Exception( "PHP sockets extension missing 
from PHP CLI mode." );
+               }
+               $sock = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
+               if ( $sock === false ) {
+                       throw new Exception( "socket_create(): " . 
socket_strerror( socket_last_error() ) );
+               }
+               socket_set_option( $sock, SOL_SOCKET, SO_REUSEADDR, 1 ); // 
bypass 2MLS
+               if ( socket_bind( $sock, $config['address'], $config['port'] ) 
=== false ) {
+                       throw new Exception( "socket_bind(): " .
+                               socket_strerror( socket_last_error( $sock ) ) );
+               } elseif ( socket_listen( $sock, $backlog ) === false ) {
+                       throw new Exception( "socket_listen(): " .
+                               socket_strerror( socket_last_error( $sock ) ) );
+               }
+               $this->sock = $sock;
+
+               $this->startTime = time();
+       }
+
+       /**
+        * @return void
+        */
+       public function main() {
+               // Create a list of all the clients that will be connected to 
us.
+               $clients = array( $this->sock ); // start off with listening 
socket
+               do {
+                       // Create a copy, so $clients doesn't get modified by 
socket_select()
+                       $read = $clients; // clients-with-data
+                       // Get a list of all the clients that have data to be 
read from
+                       $changed = socket_select( $read, $write = NULL, $except 
= NULL, NULL );
+                       if ( $changed === false ) {
+                               trigger_error( 'socket_listen(): ' . 
socket_strerror( socket_last_error() ) );
+                               continue;
+                       } elseif ( $changed < 1 ) {
+                               continue; // wait
+                       }
+                       // Check if there is a client trying to connect...
+                       if ( in_array( $this->sock, $read ) && count( $clients 
) < $this->maxClients ) {
+                               // Accept the new client...
+                               $newsock = socket_accept( $this->sock );
+                               socket_set_option( $newsock, SOL_SOCKET, 
SO_RCVTIMEO, $this->connTimeout );
+                               socket_set_option( $newsock, SOL_SOCKET, 
SO_SNDTIMEO, $this->connTimeout );
+                               $clients[] = $newsock;
+                               // Remove the listening socket from the 
clients-with-data array...
+                               $key = array_search( $this->sock, $read );
+                               unset( $read[$key] );
+                       }
+                       // Loop through all the clients that have data to 
read...
+                       foreach ( $read as $read_sock ) {
+                               // Read until newline or 65535 bytes are 
recieved.
+                               // socket_read show errors when the client is 
disconnected.
+                               $data = @socket_read( $read_sock, 65535, 
PHP_NORMAL_READ );
+                               // Check if the client is disconnected
+                               if ( $data === false ) {
+                                       // Remove client for $clients array
+                                       $key = array_search( $read_sock, 
$clients );
+                                       unset( $clients[$key] );
+                                       // Remove socket's session from 
tracking (if it exists)
+                                       $session = array_search( $read_sock, 
$this->sessions );
+                                       if ( $session !== false ) {
+                                               unset( 
$this->sessions[$session] );
+                                               // Record recently killed 
sessions that still have locks
+                                               if ( isset( 
$this->sessionIndexSh[$session] )
+                                                       || isset( 
$this->sessionIndexEx[$session] ) )
+                                               {
+                                                       
$this->deadSessions[$session] = time();
+                                               }
+                                       }
+                               } else {
+                                       // Perform the requested command...
+                                       $response = $this->doCommand( trim( 
$data ), $read_sock );
+                                       // Send the response to the client...
+                                       if ( socket_write( $read_sock, 
"$response\n" ) === false ) {
+                                               trigger_error( 'socket_write(): 
' .
+                                                       socket_strerror( 
socket_last_error( $read_sock ) ) );
+                                       }
+                               }
+                       }
+                       // Prune dead locks every 10 socket events...
+                       if ( ++$this->ticks >= 9 ) {
+                               $this->ticks = 0;
+                               $this->purgeExpiredLocks();
+                       }
+               } while ( true );
+       }
+
+       /**
+        * @param $data string
+        * @param $sourceSock resource
+        * @return string
+        */
+       protected function doCommand( $data, $sourceSock ) {
+               $cmdArr = $this->getCommand( $data );
+               if ( is_string( $cmdArr ) ) {
+                       return $cmdArr; // error
+               }
+               list( $function, $session, $type, $resources ) = $cmdArr;
+               // On first command, track the session => sock correspondence
+               if ( !isset( $this->sessions[$session] ) ) {
+                       $this->sessions[$session] = $sourceSock;
+               }
+               if ( $function === 'ACQUIRE' ) {
+                       return $this->lock( $session, $type, $resources );
+               } elseif ( $function === 'RELEASE' ) {
+                       return $this->unlock( $session, $type, $resources );
+               } elseif ( $function === 'RELEASE_ALL' ) {
+                       return $this->release( $session );
+               } elseif ( $function === 'STAT' ) {
+                       return $this->stat();
+               }
+               return 'INTERNAL_ERROR';
+       }
+
+       /**
+        * @param $data string
+        * @return Array
+        */
+       protected function getCommand( $data ) {
+               $m = explode( ':', $data ); // <session, key, command, type, 
values>
+               if ( count( $m ) == 5 ) {
+                       list( $session, $key, $command, $type, $values ) = $m;
+                       if ( sha1( $session . $command . $type . $values . 
$this->authKey ) !== $key ) {
+                               return 'BAD_KEY';
+                       } elseif ( strlen( $session ) !== 40 ) {
+                               return 'BAD_SESSION';
+                       }
+                       $values = explode( '|', $values );
+                       if ( $command === 'ACQUIRE' ) {
+                               $needsLockArgs = true;
+                       } elseif ( $command === 'RELEASE' ) {
+                               $needsLockArgs = true;
+                       } elseif ( $command === 'RELEASE_ALL' ) {
+                               $needsLockArgs = false;
+                       } elseif ( $command === 'STAT' ) {
+                               $needsLockArgs = false;
+                       } else {
+                               return 'BAD_COMMAND';
+                       }
+                       if ( $needsLockArgs ) {
+                               if ( $type !== 'SH' && $type !== 'EX' ) {
+                                       return 'BAD_TYPE';
+                               }
+                               foreach ( $values as $value ) {
+                                       if ( strlen( $value ) !== 40 ) {
+                                               return 'BAD_FORMAT';
+                                       }
+                               }
+                       }
+                       return array( $command, $session, $type, $values );
+               }
+               return 'BAD_FORMAT';
+       }
+
+       /**
+        * @param $session string
+        * @param $type string
+        * @param $keys Array
+        * @return string
+        */
+       protected function lock( $session, $type, $keys ) {
+               if ( $this->lockCount >= $this->maxLocks ) {
+                       return 'TOO_MANY_LOCKS';
+               }
+               if ( $type === 'SH' ) {
+                       // Check if any keys are already write-locked...
+                       foreach ( $keys as $key ) {
+                               if ( isset( $this->exLocks[$key] ) && 
$this->exLocks[$key] !== $session ) {
+                                       return 'CANT_ACQUIRE';
+                               }
+                       }
+                       // Acquire the read-locks...
+                       foreach ( $keys as $key ) {
+                               $this->set_sh_lock( $key, $session );
+                       }
+                       return 'ACQUIRED';
+               } elseif ( $type === 'EX' ) {
+                       // Check if any keys are already read-locked or 
write-locked...
+                       foreach ( $keys as $key ) {
+                               if ( isset( $this->exLocks[$key] ) && 
$this->exLocks[$key] !== $session ) {
+                                       return 'CANT_ACQUIRE';
+                               }
+                               if ( isset( $this->shLocks[$key] ) ) {
+                                       foreach ( $this->shLocks[$key] as 
$otherSession => $x ) {
+                                               if ( $otherSession !== $session 
) {
+                                                       return 'CANT_ACQUIRE';
+                                               }
+                                       }
+                               }
+                       }
+                       // Acquire the write-locks...
+                       foreach ( $keys as $key ) {
+                               $this->set_ex_lock( $key, $session );
+                       }
+                       return 'ACQUIRED';
+               }
+               return 'INTERNAL_ERROR';
+       }
+
+       /**
+        * @param $session string
+        * @param $type string
+        * @param $keys Array
+        * @return string
+        */
+       protected function unlock( $session, $type, $keys ) {
+               if ( $type === 'SH' ) {
+                       foreach ( $keys as $key ) {
+                               $this->unset_sh_lock( $key, $session );
+                       }
+                       return 'RELEASED';
+               } elseif ( $type === 'EX' ) {
+                       foreach ( $keys as $key ) {
+                               $this->unset_ex_lock( $key, $session );
+                       }
+                       return 'RELEASED';
+               }
+               return 'INTERNAL_ERROR';
+       }
+
+       /**
+        * @param $session string
+        * @return string
+        */
+       protected function release( $session ) {
+               if ( isset( $this->sessionIndexSh[$session] ) ) {
+                       foreach ( $this->sessionIndexSh[$session] as $key => $x 
) {
+                               $this->unset_sh_lock( $key, $session );
+                       }
+               }
+               if ( isset( $this->sessionIndexEx[$session] ) ) {
+                       foreach ( $this->sessionIndexEx[$session] as $key => $x 
) {
+                               $this->unset_ex_lock( $key, $session );
+                       }
+               }
+               return 'RELEASED_ALL';
+       }
+
+       /**
+        * @return string
+        */
+       protected function stat() {
+               return ( time() - $this->startTime ) . ':' . memory_get_usage();
+       }
+
+       /**
+        * Clear locks for sessions that have been dead for a while
+        *
+        * @return void
+        */
+       protected function purgeExpiredLocks() {
+               $now = time();
+               foreach ( $this->deadSessions as $session => $timestamp ) {
+                       if ( ( $now - $timestamp ) > $this->lockTimeout ) {
+                               $this->release( $session );
+                               unset( $this->deadSessions[$session] );
+                       }
+               }
+       }
+
+       /**
+        * @param $key string
+        * @param $session string
+        * @return void
+        */
+       protected function set_sh_lock( $key, $session ) {
+               if ( !isset( $this->shLocks[$key][$session] ) ) {
+                       $this->shLocks[$key][$session] = 1;
+                       $this->sessionIndexSh[$session][$key] = 1;
+                       ++$this->lockCount; // we are adding a lock
+               }
+       }
+
+       /**
+        * @param $key string
+        * @param $session string
+        * @return void
+        */
+       protected function set_ex_lock( $key, $session ) {
+               if ( !isset( $this->exLocks[$key][$session] ) ) {
+                       $this->exLocks[$key] = $session;
+                       $this->sessionIndexEx[$session][$key] = 1;
+                       ++$this->lockCount; // we are adding a lock
+               }
+       }
+
+       /**
+        * @param $key string
+        * @param $session string
+        * @return void
+        */
+       protected function unset_sh_lock( $key, $session ) {
+               if ( isset( $this->shLocks[$key][$session] ) ) {
+                       unset( $this->shLocks[$key][$session] );
+                       if ( !count( $this->shLocks[$key] ) ) {
+                               unset( $this->shLocks[$key] );
+                       }
+                       unset( $this->sessionIndexSh[$session][$key] );
+                       if ( !count( $this->sessionIndexSh[$session] ) ) {
+                               unset( $this->sessionIndexSh[$session] );
+                       }
+                       --$this->lockCount;
+               }
+       }
+
+       /**
+        * @param $key string
+        * @param $session string
+        * @return void
+        */
+       protected function unset_ex_lock( $key, $session ) {
+               if ( isset( $this->exLocks[$key] ) && $this->exLocks[$key] === 
$session ) {
+                       unset( $this->exLocks[$key] );
+                       unset( $this->sessionIndexEx[$session][$key] );
+                       if ( !count( $this->sessionIndexEx[$session] ) ) {
+                               unset( $this->sessionIndexEx[$session] );
+                       }
+                       --$this->lockCount;
+               }
+       }
+}


Property changes on: 
branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php
___________________________________________________________________
Added: svn:eol-style
   + native


_______________________________________________
MediaWiki-CVS mailing list
MediaWiki-CVS@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to