MediaWiki  1.23.1
MemcLockManager.php
Go to the documentation of this file.
1 <?php
40  protected $lockTypeMap = array(
41  self::LOCK_SH => self::LOCK_SH,
42  self::LOCK_UW => self::LOCK_SH,
43  self::LOCK_EX => self::LOCK_EX
44  );
45 
47  protected $bagOStuffs = array();
48 
50  protected $serversUp = array();
51 
53  protected $session = '';
54 
66  public function __construct( array $config ) {
67  parent::__construct( $config );
68 
69  // Sanitize srvsByBucket config to prevent PHP errors
70  $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
71  $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
72 
73  $memcConfig = isset( $config['memcConfig'] )
74  ? $config['memcConfig']
75  : array( 'class' => 'MemcachedPhpBagOStuff' );
76 
77  foreach ( $config['lockServers'] as $name => $address ) {
78  $params = array( 'servers' => array( $address ) ) + $memcConfig;
80  if ( $cache instanceof MemcachedBagOStuff ) {
81  $this->bagOStuffs[$name] = $cache;
82  } else {
83  throw new MWException(
84  'Only MemcachedBagOStuff classes are supported by MemcLockManager.' );
85  }
86  }
87 
88  $this->session = wfRandomString( 32 );
89  }
90 
91  // @todo Change this code to work in one batch
92  protected function getLocksOnServer( $lockSrv, array $pathsByType ) {
93  $status = Status::newGood();
94 
95  $lockedPaths = array();
96  foreach ( $pathsByType as $type => $paths ) {
97  $status->merge( $this->doGetLocksOnServer( $lockSrv, $paths, $type ) );
98  if ( $status->isOK() ) {
99  $lockedPaths[$type] = isset( $lockedPaths[$type] )
100  ? array_merge( $lockedPaths[$type], $paths )
101  : $paths;
102  } else {
103  foreach ( $lockedPaths as $lType => $lPaths ) {
104  $status->merge( $this->doFreeLocksOnServer( $lockSrv, $lPaths, $lType ) );
105  }
106  break;
107  }
108  }
109 
110  return $status;
111  }
112 
113  // @todo Change this code to work in one batch
114  protected function freeLocksOnServer( $lockSrv, array $pathsByType ) {
115  $status = Status::newGood();
116 
117  foreach ( $pathsByType as $type => $paths ) {
118  $status->merge( $this->doFreeLocksOnServer( $lockSrv, $paths, $type ) );
119  }
120 
121  return $status;
122  }
123 
131  protected function doGetLocksOnServer( $lockSrv, array $paths, $type ) {
132  $status = Status::newGood();
133 
134  $memc = $this->getCache( $lockSrv );
135  $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
136 
137  // Lock all of the active lock record keys...
138  if ( !$this->acquireMutexes( $memc, $keys ) ) {
139  foreach ( $paths as $path ) {
140  $status->fatal( 'lockmanager-fail-acquirelock', $path );
141  }
142 
143  return $status;
144  }
145 
146  // Fetch all the existing lock records...
147  $lockRecords = $memc->getMulti( $keys );
148 
149  $now = time();
150  // Check if the requested locks conflict with existing ones...
151  foreach ( $paths as $path ) {
152  $locksKey = $this->recordKeyForPath( $path );
153  $locksHeld = isset( $lockRecords[$locksKey] )
154  ? self::sanitizeLockArray( $lockRecords[$locksKey] )
155  : self::newLockArray(); // init
156  foreach ( $locksHeld[self::LOCK_EX] as $session => $expiry ) {
157  if ( $expiry < $now ) { // stale?
158  unset( $locksHeld[self::LOCK_EX][$session] );
159  } elseif ( $session !== $this->session ) {
160  $status->fatal( 'lockmanager-fail-acquirelock', $path );
161  }
162  }
163  if ( $type === self::LOCK_EX ) {
164  foreach ( $locksHeld[self::LOCK_SH] as $session => $expiry ) {
165  if ( $expiry < $now ) { // stale?
166  unset( $locksHeld[self::LOCK_SH][$session] );
167  } elseif ( $session !== $this->session ) {
168  $status->fatal( 'lockmanager-fail-acquirelock', $path );
169  }
170  }
171  }
172  if ( $status->isOK() ) {
173  // Register the session in the lock record array
175  // We will update this record if none of the other locks conflict
176  $lockRecords[$locksKey] = $locksHeld;
177  }
178  }
179 
180  // If there were no lock conflicts, update all the lock records...
181  if ( $status->isOK() ) {
182  foreach ( $paths as $path ) {
183  $locksKey = $this->recordKeyForPath( $path );
184  $locksHeld = $lockRecords[$locksKey];
185  $ok = $memc->set( $locksKey, $locksHeld, 7 * 86400 );
186  if ( !$ok ) {
187  $status->fatal( 'lockmanager-fail-acquirelock', $path );
188  } else {
189  wfDebug( __METHOD__ . ": acquired lock on key $locksKey.\n" );
190  }
191  }
192  }
193 
194  // Unlock all of the active lock record keys...
195  $this->releaseMutexes( $memc, $keys );
196 
197  return $status;
198  }
199 
207  protected function doFreeLocksOnServer( $lockSrv, array $paths, $type ) {
208  $status = Status::newGood();
209 
210  $memc = $this->getCache( $lockSrv );
211  $keys = array_map( array( $this, 'recordKeyForPath' ), $paths ); // lock records
212 
213  // Lock all of the active lock record keys...
214  if ( !$this->acquireMutexes( $memc, $keys ) ) {
215  foreach ( $paths as $path ) {
216  $status->fatal( 'lockmanager-fail-releaselock', $path );
217  }
218 
219  return $status;
220  }
221 
222  // Fetch all the existing lock records...
223  $lockRecords = $memc->getMulti( $keys );
224 
225  // Remove the requested locks from all records...
226  foreach ( $paths as $path ) {
227  $locksKey = $this->recordKeyForPath( $path ); // lock record
228  if ( !isset( $lockRecords[$locksKey] ) ) {
229  $status->warning( 'lockmanager-fail-releaselock', $path );
230  continue; // nothing to do
231  }
232  $locksHeld = self::sanitizeLockArray( $lockRecords[$locksKey] );
233  if ( isset( $locksHeld[$type][$this->session] ) ) {
234  unset( $locksHeld[$type][$this->session] ); // unregister this session
235  if ( $locksHeld === self::newLockArray() ) {
236  $ok = $memc->delete( $locksKey );
237  } else {
238  $ok = $memc->set( $locksKey, $locksHeld );
239  }
240  if ( !$ok ) {
241  $status->fatal( 'lockmanager-fail-releaselock', $path );
242  }
243  } else {
244  $status->warning( 'lockmanager-fail-releaselock', $path );
245  }
246  wfDebug( __METHOD__ . ": released lock on key $locksKey.\n" );
247  }
248 
249  // Unlock all of the active lock record keys...
250  $this->releaseMutexes( $memc, $keys );
251 
252  return $status;
253  }
254 
259  protected function releaseAllLocks() {
260  return Status::newGood(); // not supported
261  }
262 
268  protected function isServerUp( $lockSrv ) {
269  return (bool)$this->getCache( $lockSrv );
270  }
271 
278  protected function getCache( $lockSrv ) {
279  $memc = null;
280  if ( isset( $this->bagOStuffs[$lockSrv] ) ) {
281  $memc = $this->bagOStuffs[$lockSrv];
282  if ( !isset( $this->serversUp[$lockSrv] ) ) {
283  $this->serversUp[$lockSrv] = $memc->set( __CLASS__ . ':ping', 1, 1 );
284  if ( !$this->serversUp[$lockSrv] ) {
285  trigger_error( __METHOD__ . ": Could not contact $lockSrv.", E_USER_WARNING );
286  }
287  }
288  if ( !$this->serversUp[$lockSrv] ) {
289  return null; // server appears to be down
290  }
291  }
292 
293  return $memc;
294  }
295 
300  protected function recordKeyForPath( $path ) {
301  return implode( ':', array( __CLASS__, 'locks', $this->sha1Base36Absolute( $path ) ) );
302  }
303 
307  protected static function newLockArray() {
308  return array( self::LOCK_SH => array(), self::LOCK_EX => array() );
309  }
310 
315  protected static function sanitizeLockArray( $a ) {
316  if ( is_array( $a ) && isset( $a[self::LOCK_EX] ) && isset( $a[self::LOCK_SH] ) ) {
317  return $a;
318  } else {
319  trigger_error( __METHOD__ . ": reset invalid lock array.", E_USER_WARNING );
320 
321  return self::newLockArray();
322  }
323  }
324 
330  protected function acquireMutexes( MemcachedBagOStuff $memc, array $keys ) {
331  $lockedKeys = array();
332 
333  // Acquire the keys in lexicographical order, to avoid deadlock problems.
334  // If P1 is waiting to acquire a key P2 has, P2 can't also be waiting for a key P1 has.
335  sort( $keys );
336 
337  // Try to quickly loop to acquire the keys, but back off after a few rounds.
338  // This reduces memcached spam, especially in the rare case where a server acquires
339  // some lock keys and dies without releasing them. Lock keys expire after a few minutes.
340  $rounds = 0;
341  $start = microtime( true );
342  do {
343  if ( ( ++$rounds % 4 ) == 0 ) {
344  usleep( 1000 * 50 ); // 50 ms
345  }
346  foreach ( array_diff( $keys, $lockedKeys ) as $key ) {
347  if ( $memc->add( "$key:mutex", 1, 180 ) ) { // lock record
348  $lockedKeys[] = $key;
349  } else {
350  continue; // acquire in order
351  }
352  }
353  } while ( count( $lockedKeys ) < count( $keys ) && ( microtime( true ) - $start ) <= 3 );
354 
355  if ( count( $lockedKeys ) != count( $keys ) ) {
356  $this->releaseMutexes( $memc, $lockedKeys ); // failed; release what was locked
357  return false;
358  }
359 
360  return true;
361  }
362 
367  protected function releaseMutexes( MemcachedBagOStuff $memc, array $keys ) {
368  foreach ( $keys as $key ) {
369  $memc->delete( "$key:mutex" );
370  }
371  }
372 
376  function __destruct() {
377  while ( count( $this->locksHeld ) ) {
378  foreach ( $this->locksHeld as $path => $locks ) {
379  $this->doUnlock( array( $path ), self::LOCK_EX );
380  $this->doUnlock( array( $path ), self::LOCK_SH );
381  }
382  }
383  }
384 }
MemcLockManager\doGetLocksOnServer
doGetLocksOnServer( $lockSrv, array $paths, $type)
Definition: MemcLockManager.php:127
LockManager\$locksHeld
array $locksHeld
Map of (resource path => lock type => count) *.
Definition: LockManager.php:52
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
MemcLockManager\freeLocksOnServer
freeLocksOnServer( $lockSrv, array $pathsByType)
Get a connection to a lock server and release locks on $paths.
Definition: MemcLockManager.php:110
MemcachedBagOStuff
Base class for memcached clients.
Definition: MemcachedBagOStuff.php:29
MemcLockManager\$session
string $session
random UUID *
Definition: MemcLockManager.php:49
MemcLockManager\sanitizeLockArray
static sanitizeLockArray( $a)
Definition: MemcLockManager.php:311
MemcLockManager\releaseAllLocks
releaseAllLocks()
Definition: MemcLockManager.php:255
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
$params
$params
Definition: styleTest.css.php:40
MemcLockManager\acquireMutexes
acquireMutexes(MemcachedBagOStuff $memc, array $keys)
Definition: MemcLockManager.php:326
MemcLockManager\$bagOStuffs
array $bagOStuffs
Map server names to MemcachedBagOStuff objects *.
Definition: MemcLockManager.php:45
MemcLockManager\__construct
__construct(array $config)
Construct a new instance from configuration.
Definition: MemcLockManager.php:62
MemcLockManager\$serversUp
array $serversUp
(server name => bool) *
Definition: MemcLockManager.php:47
LockManager\$lockTTL
$lockTTL
Definition: LockManager.php:55
MemcLockManager\getCache
getCache( $lockSrv)
Get the MemcachedBagOStuff object for a $lockSrv.
Definition: MemcLockManager.php:274
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
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
MemcLockManager\isServerUp
isServerUp( $lockSrv)
Definition: MemcLockManager.php:264
MemcLockManager\doFreeLocksOnServer
doFreeLocksOnServer( $lockSrv, array $paths, $type)
Definition: MemcLockManager.php:203
QuorumLockManager\doUnlock
doUnlock(array $paths, $type)
Unlock resources with the given keys and lock type.
Definition: QuorumLockManager.php:40
$ok
$ok
Definition: UtfNormalTest.php:71
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:933
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
MemcLockManager\__destruct
__destruct()
Make sure remaining locks get cleared for sanity.
Definition: MemcLockManager.php:372
$cache
$cache
Definition: mcc.php:32
MemcLockManager\recordKeyForPath
recordKeyForPath( $path)
Definition: MemcLockManager.php:296
$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
MemcLockManager\newLockArray
static newLockArray()
Definition: MemcLockManager.php:303
MemcLockManager
Manage locks using memcached servers.
Definition: MemcLockManager.php:38
$keys
$keys
Definition: testCompression.php:63
MemcachedBagOStuff\add
add( $key, $value, $exptime=0)
Definition: MemcachedBagOStuff.php:105
ObjectCache\newFromParams
static newFromParams( $params)
Create a new cache object from parameters.
Definition: ObjectCache.php:83
MemcLockManager\releaseMutexes
releaseMutexes(MemcachedBagOStuff $memc, array $keys)
Definition: MemcLockManager.php:363
MemcLockManager\$lockTypeMap
array $lockTypeMap
Mapping of lock types to the type actually used *.
Definition: MemcLockManager.php:39
MemcLockManager\getLocksOnServer
getLocksOnServer( $lockSrv, array $pathsByType)
Get a connection to a lock server and acquire locks.
Definition: MemcLockManager.php:88
MemcachedBagOStuff\delete
delete( $key, $time=0)
Definition: MemcachedBagOStuff.php:95
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:300
$type
$type
Definition: testCompression.php:46