MediaWiki  1.23.0
DBLockManager.php
Go to the documentation of this file.
1 <?php
39 abstract class DBLockManager extends QuorumLockManager {
41  protected $dbServers; // (DB name => server config array)
43  protected $statusCache;
44 
45  protected $lockExpiry; // integer number of seconds
46  protected $safeDelay; // integer number of seconds
47 
48  protected $session = 0; // random integer
50  protected $conns = array();
51 
74  public function __construct( array $config ) {
75  parent::__construct( $config );
76 
77  $this->dbServers = isset( $config['dbServers'] )
78  ? $config['dbServers']
79  : array(); // likely just using 'localDBMaster'
80  // Sanitize srvsByBucket config to prevent PHP errors
81  $this->srvsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
82  $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
83 
84  if ( isset( $config['lockExpiry'] ) ) {
85  $this->lockExpiry = $config['lockExpiry'];
86  } else {
87  $met = ini_get( 'max_execution_time' );
88  $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
89  }
90  $this->safeDelay = ( $this->lockExpiry <= 0 )
91  ? 60 // pick a safe-ish number to match DB timeout default
92  : $this->lockExpiry; // cover worst case
93 
94  foreach ( $this->srvsByBucket as $bucket ) {
95  if ( count( $bucket ) > 1 ) { // multiple peers
96  // Tracks peers that couldn't be queried recently to avoid lengthy
97  // connection timeouts. This is useless if each bucket has one peer.
98  try {
99  $this->statusCache = ObjectCache::newAccelerator( array() );
100  } catch ( MWException $e ) {
101  trigger_error( __CLASS__ .
102  " using multiple DB peers without apc, xcache, or wincache." );
103  }
104  break;
105  }
106  }
107 
108  $this->session = wfRandomString( 31 );
109  }
110 
111  // @TODO: change this code to work in one batch
112  protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
113  $status = Status::newGood();
114  foreach ( $pathsByType as $type => $paths ) {
115  $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
116  }
117 
118  return $status;
119  }
120 
121  protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
122  return Status::newGood();
123  }
124 
130  protected function isServerUp( $lockSrv ) {
131  if ( !$this->cacheCheckFailures( $lockSrv ) ) {
132  return false; // recent failure to connect
133  }
134  try {
135  $this->getConnection( $lockSrv );
136  } catch ( DBError $e ) {
137  $this->cacheRecordFailure( $lockSrv );
138 
139  return false; // failed to connect
140  }
141 
142  return true;
143  }
144 
152  protected function getConnection( $lockDb ) {
153  if ( !isset( $this->conns[$lockDb] ) ) {
154  $db = null;
155  if ( $lockDb === 'localDBMaster' ) {
156  $lb = wfGetLBFactory()->getMainLB( $this->domain );
157  $db = $lb->getConnection( DB_MASTER, array(), $this->domain );
158  } elseif ( isset( $this->dbServers[$lockDb] ) ) {
159  $config = $this->dbServers[$lockDb];
160  $db = DatabaseBase::factory( $config['type'], $config );
161  }
162  if ( !$db ) {
163  return null; // config error?
164  }
165  $this->conns[$lockDb] = $db;
166  $this->conns[$lockDb]->clearFlag( DBO_TRX );
167  # If the connection drops, try to avoid letting the DB rollback
168  # and release the locks before the file operations are finished.
169  # This won't handle the case of DB server restarts however.
170  $options = array();
171  if ( $this->lockExpiry > 0 ) {
172  $options['connTimeout'] = $this->lockExpiry;
173  }
174  $this->conns[$lockDb]->setSessionOptions( $options );
175  $this->initConnection( $lockDb, $this->conns[$lockDb] );
176  }
177  if ( !$this->conns[$lockDb]->trxLevel() ) {
178  $this->conns[$lockDb]->begin( __METHOD__ ); // start transaction
179  }
180 
181  return $this->conns[$lockDb];
182  }
183 
191  protected function initConnection( $lockDb, DatabaseBase $db ) {
192  }
193 
201  protected function cacheCheckFailures( $lockDb ) {
202  return ( $this->statusCache && $this->safeDelay > 0 )
203  ? !$this->statusCache->get( $this->getMissKey( $lockDb ) )
204  : true;
205  }
206 
213  protected function cacheRecordFailure( $lockDb ) {
214  return ( $this->statusCache && $this->safeDelay > 0 )
215  ? $this->statusCache->set( $this->getMissKey( $lockDb ), 1, $this->safeDelay )
216  : true;
217  }
218 
225  protected function getMissKey( $lockDb ) {
226  $lockDb = ( $lockDb === 'localDBMaster' ) ? wfWikiID() : $lockDb; // non-relative
227  return 'dblockmanager:downservers:' . str_replace( ' ', '_', $lockDb );
228  }
229 
233  function __destruct() {
234  $this->releaseAllLocks();
235  foreach ( $this->conns as $db ) {
236  $db->close();
237  }
238  }
239 }
240 
247 class MySqlLockManager extends DBLockManager {
249  protected $lockTypeMap = array(
250  self::LOCK_SH => self::LOCK_SH,
251  self::LOCK_UW => self::LOCK_SH,
252  self::LOCK_EX => self::LOCK_EX
253  );
254 
259  protected function initConnection( $lockDb, DatabaseBase $db ) {
260  # Let this transaction see lock rows from other transactions
261  $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
262  }
263 
274  protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
275  $status = Status::newGood();
276 
277  $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
278 
279  $keys = array(); // list of hash keys for the paths
280  $data = array(); // list of rows to insert
281  $checkEXKeys = array(); // list of hash keys that this has no EX lock on
282  # Build up values for INSERT clause
283  foreach ( $paths as $path ) {
284  $key = $this->sha1Base36Absolute( $path );
285  $keys[] = $key;
286  $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session );
287  if ( !isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
288  $checkEXKeys[] = $key;
289  }
290  }
291 
292  # Block new writers (both EX and SH locks leave entries here)...
293  $db->insert( 'filelocks_shared', $data, __METHOD__, array( 'IGNORE' ) );
294  # Actually do the locking queries...
295  if ( $type == self::LOCK_SH ) { // reader locks
296  $blocked = false;
297  # Bail if there are any existing writers...
298  if ( count( $checkEXKeys ) ) {
299  $blocked = $db->selectField( 'filelocks_exclusive', '1',
300  array( 'fle_key' => $checkEXKeys ),
301  __METHOD__
302  );
303  }
304  # Other prospective writers that haven't yet updated filelocks_exclusive
305  # will recheck filelocks_shared after doing so and bail due to this entry.
306  } else { // writer locks
307  $encSession = $db->addQuotes( $this->session );
308  # Bail if there are any existing writers...
309  # This may detect readers, but the safe check for them is below.
310  # Note: if two writers come at the same time, both bail :)
311  $blocked = $db->selectField( 'filelocks_shared', '1',
312  array( 'fls_key' => $keys, "fls_session != $encSession" ),
313  __METHOD__
314  );
315  if ( !$blocked ) {
316  # Build up values for INSERT clause
317  $data = array();
318  foreach ( $keys as $key ) {
319  $data[] = array( 'fle_key' => $key );
320  }
321  # Block new readers/writers...
322  $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
323  # Bail if there are any existing readers...
324  $blocked = $db->selectField( 'filelocks_shared', '1',
325  array( 'fls_key' => $keys, "fls_session != $encSession" ),
326  __METHOD__
327  );
328  }
329  }
330 
331  if ( $blocked ) {
332  foreach ( $paths as $path ) {
333  $status->fatal( 'lockmanager-fail-acquirelock', $path );
334  }
335  }
336 
337  return $status;
338  }
339 
344  protected function releaseAllLocks() {
345  $status = Status::newGood();
346 
347  foreach ( $this->conns as $lockDb => $db ) {
348  if ( $db->trxLevel() ) { // in transaction
349  try {
350  $db->rollback( __METHOD__ ); // finish transaction and kill any rows
351  } catch ( DBError $e ) {
352  $status->fatal( 'lockmanager-fail-db-release', $lockDb );
353  }
354  }
355  }
356 
357  return $status;
358  }
359 }
360 
367 class PostgreSqlLockManager extends DBLockManager {
369  protected $lockTypeMap = array(
370  self::LOCK_SH => self::LOCK_SH,
371  self::LOCK_UW => self::LOCK_SH,
372  self::LOCK_EX => self::LOCK_EX
373  );
374 
375  protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
376  $status = Status::newGood();
377  if ( !count( $paths ) ) {
378  return $status; // nothing to lock
379  }
380 
381  $db = $this->getConnection( $lockSrv ); // checked in isServerUp()
382  $bigints = array_unique( array_map(
383  function ( $key ) {
384  return wfBaseConvert( substr( $key, 0, 15 ), 16, 10 );
385  },
386  array_map( array( $this, 'sha1Base16Absolute' ), $paths )
387  ) );
388 
389  // Try to acquire all the locks...
390  $fields = array();
391  foreach ( $bigints as $bigint ) {
392  $fields[] = ( $type == self::LOCK_SH )
393  ? "pg_try_advisory_lock_shared({$db->addQuotes( $bigint )}) AS K$bigint"
394  : "pg_try_advisory_lock({$db->addQuotes( $bigint )}) AS K$bigint";
395  }
396  $res = $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
397  $row = (array)$res->fetchObject();
398 
399  if ( in_array( 'f', $row ) ) {
400  // Release any acquired locks if some could not be acquired...
401  $fields = array();
402  foreach ( $row as $kbigint => $ok ) {
403  if ( $ok === 't' ) { // locked
404  $bigint = substr( $kbigint, 1 ); // strip off the "K"
405  $fields[] = ( $type == self::LOCK_SH )
406  ? "pg_advisory_unlock_shared({$db->addQuotes( $bigint )})"
407  : "pg_advisory_unlock({$db->addQuotes( $bigint )})";
408  }
409  }
410  if ( count( $fields ) ) {
411  $db->query( 'SELECT ' . implode( ', ', $fields ), __METHOD__ );
412  }
413  foreach ( $paths as $path ) {
414  $status->fatal( 'lockmanager-fail-acquirelock', $path );
415  }
416  }
417 
418  return $status;
419  }
420 
425  protected function releaseAllLocks() {
426  $status = Status::newGood();
427 
428  foreach ( $this->conns as $lockDb => $db ) {
429  try {
430  $db->query( "SELECT pg_advisory_unlock_all()", __METHOD__ );
431  } catch ( DBError $e ) {
432  $status->fatal( 'lockmanager-fail-db-release', $lockDb );
433  }
434  }
435 
436  return $status;
437  }
438 }
DB_MASTER
const DB_MASTER
Definition: Defines.php:56
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
DBLockManager\isServerUp
isServerUp( $lockSrv)
Definition: DBLockManager.php:127
LockManager\LOCK_SH
const LOCK_SH
Lock types; stronger locks have higher values.
Definition: LockManager.php:58
DatabaseBase\query
query( $sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:1001
DBLockManager\cacheRecordFailure
cacheRecordFailure( $lockDb)
Log a lock request failure to the cache.
Definition: DBLockManager.php:210
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
DBLockManager\__construct
__construct(array $config)
Construct a new instance from configuration.
Definition: DBLockManager.php:71
DBLockManager\getLocksOnServer
getLocksOnServer( $lockSrv, array $pathsByType)
Get a connection to a lock server and acquire locks.
Definition: DBLockManager.php:109
DBLockManager\$safeDelay
$safeDelay
Definition: DBLockManager.php:44
MySqlLockManager\releaseAllLocks
releaseAllLocks()
Definition: DBLockManager.php:340
DBLockManager\cacheCheckFailures
cacheCheckFailures( $lockDb)
Checks if the DB has not recently had connection/query errors.
Definition: DBLockManager.php:198
DBLockManager\$dbServers
array $dbServers
Map of DB names to server config *.
Definition: DBLockManager.php:40
$lb
if( $wgAPIRequestLog) $lb
Definition: api.php:126
DatabaseBase\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
Definition: Database.php:2477
DatabaseBase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=array())
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1281
QuorumLockManager
Version of LockManager that uses a quorum from peer servers for locks.
Definition: QuorumLockManager.php:31
MWException
MediaWiki exception.
Definition: MWException.php:26
LockManager\sha1Base36Absolute
sha1Base36Absolute( $path)
Get the base 36 SHA-1 of a string, padded to 31 digits.
Definition: LockManager.php:157
QuorumLockManager\releaseAllLocks
releaseAllLocks()
Release all locks that this session is holding.
DBLockManager\getMissKey
getMissKey( $lockDb)
Get a cache key for recent query misses for a DB.
Definition: DBLockManager.php:222
DBLockManager\$conns
array $conns
Map Database connections (DB name => Database) *.
Definition: DBLockManager.php:47
PostgreSqlLockManager\$lockTypeMap
array $lockTypeMap
Mapping of lock types to the type actually used *.
Definition: DBLockManager.php:364
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
DatabaseBase\factory
static factory( $dbType, $p=array())
Given a DB type, construct the name of the appropriate child class of DatabaseBase.
Definition: Database.php:808
DBError
Database error base class.
Definition: DatabaseError.php:28
DBLockManager\__destruct
__destruct()
Make sure remaining locks get cleared for sanity.
Definition: DBLockManager.php:230
DBLockManager\$session
$session
Definition: DBLockManager.php:46
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1530
$ok
$ok
Definition: UtfNormalTest.php:71
DatabaseBase\trxLevel
trxLevel()
Gets the current transaction level.
Definition: Database.php:400
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:3604
MySqlLockManager\doGetLocksOnServer
doGetLocksOnServer( $lockSrv, array $paths, $type)
Get a connection to a lock DB and acquire locks on $paths.
Definition: DBLockManager.php:270
DatabaseBase
Database abstraction object.
Definition: Database.php:219
ObjectCache\newAccelerator
static newAccelerator( $params)
Factory function referenced from DefaultSettings.php for CACHE_ACCEL.
Definition: ObjectCache.php:125
DatabaseBase\rollback
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
Definition: Database.php:3492
DBLockManager\$lockExpiry
$lockExpiry
Definition: DBLockManager.php:43
MySqlLockManager\$lockTypeMap
array $lockTypeMap
Mapping of lock types to the type actually used *.
Definition: DBLockManager.php:245
PostgreSqlLockManager\doGetLocksOnServer
doGetLocksOnServer( $lockSrv, array $paths, $type)
Definition: DBLockManager.php:370
MySqlLockManager
MySQL version of DBLockManager that supports shared locks.
Definition: DBLockManager.php:244
DBLockManager\$statusCache
BagOStuff $statusCache
Definition: DBLockManager.php:41
MySqlLockManager\initConnection
initConnection( $lockDb, DatabaseBase $db)
Definition: DBLockManager.php:255
wfGetLBFactory
& wfGetLBFactory()
Get the load balancer factory object.
Definition: GlobalFunctions.php:3669
wfBaseConvert
wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true, $engine='auto')
Convert an arbitrarily-long digit string from one numeric base to another, optionally zero-padding to...
Definition: GlobalFunctions.php:3368
$path
$path
Definition: NoLocalSettings.php:35
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
$keys
$keys
Definition: testCompression.php:63
DBLockManager\getConnection
getConnection( $lockDb)
Get (or reuse) a connection to a lock DB.
Definition: DBLockManager.php:149
DBLockManager\initConnection
initConnection( $lockDb, DatabaseBase $db)
Do additional initialization for new lock DB connection.
Definition: DBLockManager.php:188
DBLockManager
Version of LockManager based on using named/row DB locks.
Definition: DBLockManager.php:39
$e
if( $useReadline) $e
Definition: eval.php:66
PostgreSqlLockManager\releaseAllLocks
releaseAllLocks()
Definition: DBLockManager.php:420
$res
$res
Definition: database.txt:21
DBO_TRX
const DBO_TRX
Definition: Defines.php:42
PostgreSqlLockManager
PostgreSQL version of DBLockManager that supports shared locks.
Definition: DBLockManager.php:363
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:300
DatabaseBase\insert
insert( $table, $a, $fname=__METHOD__, $options=array())
INSERT wrapper, inserts an array into a table.
Definition: Database.php:1860
DBLockManager\freeLocksOnServer
freeLocksOnServer( $lockSrv, array $pathsByType)
Get a connection to a lock server and release locks on $paths.
Definition: DBLockManager.php:118
$type
$type
Definition: testCompression.php:46