summaryrefslogtreecommitdiff
path: root/includes/filerepo/backend/lockmanager/LSLockManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/filerepo/backend/lockmanager/LSLockManager.php')
-rw-r--r--includes/filerepo/backend/lockmanager/LSLockManager.php295
1 files changed, 0 insertions, 295 deletions
diff --git a/includes/filerepo/backend/lockmanager/LSLockManager.php b/includes/filerepo/backend/lockmanager/LSLockManager.php
deleted file mode 100644
index b7ac743c..00000000
--- a/includes/filerepo/backend/lockmanager/LSLockManager.php
+++ /dev/null
@@ -1,295 +0,0 @@
-<?php
-
-/**
- * Manage locks using a lock daemon server.
- *
- * 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.
- *
- * @ingroup LockManager
- * @since 1.19
- */
-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 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 = wfBaseConvert( sha1( $this->session ), 16, 36, 31 );
- }
-
- protected function doLock( array $paths, $type ) {
- $status = Status::newGood();
-
- $pathsToLock = array();
- // Get locks that need to be acquired (buckets => locks)...
- foreach ( $paths as $path ) {
- if ( isset( $this->locksHeld[$path][$type] ) ) {
- ++$this->locksHeld[$path][$type];
- } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
- $this->locksHeld[$path][$type] = 1;
- } else {
- $bucket = $this->getBucketFromKey( $path );
- $pathsToLock[$bucket][] = $path;
- }
- }
-
- $lockedPaths = array(); // files locked in this attempt
- // Attempt to acquire these locks...
- foreach ( $pathsToLock as $bucket => $paths ) {
- // Try to acquire the locks for this bucket
- $res = $this->doLockingRequestAll( $bucket, $paths, $type );
- if ( $res === 'cantacquire' ) {
- // Resources already locked by another process.
- // Abort and unlock everything we just locked.
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-acquirelock', $path );
- }
- $status->merge( $this->doUnlock( $lockedPaths, $type ) );
- return $status;
- } elseif ( $res !== true ) {
- // Couldn't contact any servers for this bucket.
- // Abort and unlock everything we just locked.
- foreach ( $paths as $path ) {
- $status->fatal( 'lockmanager-fail-acquirelock', $path );
- }
- $status->merge( $this->doUnlock( $lockedPaths, $type ) );
- return $status;
- }
- // Record these locks as active
- foreach ( $paths as $path ) {
- $this->locksHeld[$path][$type] = 1; // locked
- }
- // Keep track of what locks were made in this attempt
- $lockedPaths = array_merge( $lockedPaths, $paths );
- }
-
- return $status;
- }
-
- protected function doUnlock( array $paths, $type ) {
- $status = Status::newGood();
-
- foreach ( $paths as $path ) {
- if ( !isset( $this->locksHeld[$path] ) ) {
- $status->warning( 'lockmanager-notlocked', $path );
- } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
- $status->warning( 'lockmanager-notlocked', $path );
- } else {
- --$this->locksHeld[$path][$type];
- if ( $this->locksHeld[$path][$type] <= 0 ) {
- unset( $this->locksHeld[$path][$type] );
- }
- if ( !count( $this->locksHeld[$path] ) ) {
- unset( $this->locksHeld[$path] ); // 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 $paths
- *
- * @param $lockSrv string
- * @param $paths Array
- * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
- * @return bool Resources able to be locked
- */
- protected function doLockingRequest( $lockSrv, array $paths, $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...
- $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
- $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 $paths 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 $paths, $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 $lockSrv ) {
- // Attempt to acquire the lock on this peer
- if ( !$this->doLockingRequest( $lockSrv, $paths, $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 resource path.
- * This should avoid throwing any exceptions.
- *
- * @param $path string
- * @return integer
- */
- protected function getBucketFromKey( $path ) {
- $prefix = substr( sha1( $path ), 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 $conn ) {
- fclose( $conn );
- }
- }
-}