MediaWiki  master
FSLockManager.php
Go to the documentation of this file.
1 <?php
36 class FSLockManager extends LockManager {
38  protected $lockTypeMap = [
39  self::LOCK_SH => self::LOCK_SH,
40  self::LOCK_UW => self::LOCK_SH,
41  self::LOCK_EX => self::LOCK_EX
42  ];
43 
45  protected $lockDir;
46 
48  protected $handles = [];
49 
51  protected $isWindows;
52 
59  function __construct( array $config ) {
60  parent::__construct( $config );
61 
62  $this->lockDir = $config['lockDirectory'];
63  $this->isWindows = ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
64  }
65 
72  protected function doLock( array $paths, $type ) {
73  $status = StatusValue::newGood();
74 
75  $lockedPaths = []; // files locked in this attempt
76  foreach ( $paths as $path ) {
77  $status->merge( $this->doSingleLock( $path, $type ) );
78  if ( $status->isOK() ) {
79  $lockedPaths[] = $path;
80  } else {
81  // Abort and unlock everything
82  $status->merge( $this->doUnlock( $lockedPaths, $type ) );
83 
84  return $status;
85  }
86  }
87 
88  return $status;
89  }
90 
97  protected function doUnlock( array $paths, $type ) {
98  $status = StatusValue::newGood();
99 
100  foreach ( $paths as $path ) {
101  $status->merge( $this->doSingleUnlock( $path, $type ) );
102  }
103 
104  return $status;
105  }
106 
114  protected function doSingleLock( $path, $type ) {
115  $status = StatusValue::newGood();
116 
117  if ( isset( $this->locksHeld[$path][$type] ) ) {
118  ++$this->locksHeld[$path][$type];
119  } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
120  $this->locksHeld[$path][$type] = 1;
121  } else {
122  if ( isset( $this->handles[$path] ) ) {
123  $handle = $this->handles[$path];
124  } else {
125  Wikimedia\suppressWarnings();
126  $handle = fopen( $this->getLockPath( $path ), 'a+' );
127  if ( !$handle && !is_dir( $this->lockDir ) ) {
128  // Create the lock directory in case it is missing
129  if ( mkdir( $this->lockDir, 0777, true ) ) {
130  $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
131  } else {
132  $this->logger->error( "Cannot create directory '{$this->lockDir}'." );
133  }
134  }
135  Wikimedia\restoreWarnings();
136  }
137  if ( $handle ) {
138  // Either a shared or exclusive lock
139  $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
140  if ( flock( $handle, $lock | LOCK_NB ) ) {
141  // Record this lock as active
142  $this->locksHeld[$path][$type] = 1;
143  $this->handles[$path] = $handle;
144  } else {
145  fclose( $handle );
146  $status->fatal( 'lockmanager-fail-acquirelock', $path );
147  }
148  } else {
149  $status->fatal( 'lockmanager-fail-openlock', $path );
150  }
151  }
152 
153  return $status;
154  }
155 
163  protected function doSingleUnlock( $path, $type ) {
164  $status = StatusValue::newGood();
165 
166  if ( !isset( $this->locksHeld[$path] ) ) {
167  $status->warning( 'lockmanager-notlocked', $path );
168  } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
169  $status->warning( 'lockmanager-notlocked', $path );
170  } else {
171  $handlesToClose = [];
172  --$this->locksHeld[$path][$type];
173  if ( $this->locksHeld[$path][$type] <= 0 ) {
174  unset( $this->locksHeld[$path][$type] );
175  }
176  if ( $this->locksHeld[$path] === [] ) {
177  unset( $this->locksHeld[$path] ); // no locks on this path
178  if ( isset( $this->handles[$path] ) ) {
179  $handlesToClose[] = $this->handles[$path];
180  unset( $this->handles[$path] );
181  }
182  }
183  // Unlock handles to release locks and delete
184  // any lock files that end up with no locks on them...
185  if ( $this->isWindows ) {
186  // Windows: for any process, including this one,
187  // calling unlink() on a locked file will fail
188  $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
189  $status->merge( $this->pruneKeyLockFiles( $path ) );
190  } else {
191  // Unix: unlink() can be used on files currently open by this
192  // process and we must do so in order to avoid race conditions
193  $status->merge( $this->pruneKeyLockFiles( $path ) );
194  $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
195  }
196  }
197 
198  return $status;
199  }
200 
206  private function closeLockHandles( $path, array $handlesToClose ) {
207  $status = StatusValue::newGood();
208  foreach ( $handlesToClose as $handle ) {
209  if ( !flock( $handle, LOCK_UN ) ) {
210  $status->fatal( 'lockmanager-fail-releaselock', $path );
211  }
212  if ( !fclose( $handle ) ) {
213  $status->warning( 'lockmanager-fail-closelock', $path );
214  }
215  }
216 
217  return $status;
218  }
219 
224  private function pruneKeyLockFiles( $path ) {
225  $status = StatusValue::newGood();
226  if ( !isset( $this->locksHeld[$path] ) ) {
227  # No locks are held for the lock file anymore
228  if ( !unlink( $this->getLockPath( $path ) ) ) {
229  $status->warning( 'lockmanager-fail-deletelock', $path );
230  }
231  unset( $this->handles[$path] );
232  }
233 
234  return $status;
235  }
236 
242  protected function getLockPath( $path ) {
243  return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
244  }
245 
249  function __destruct() {
250  while ( count( $this->locksHeld ) ) {
251  foreach ( $this->locksHeld as $path => $locks ) {
252  $this->doSingleUnlock( $path, self::LOCK_EX );
253  $this->doSingleUnlock( $path, self::LOCK_SH );
254  }
255  }
256  }
257 }
pruneKeyLockFiles( $path)
__construct(array $config)
Construct a new instance from configuration.
array $lockTypeMap
Mapping of lock types to the type actually used.
const LOCK_EX
Definition: LockManager.php:70
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
__destruct()
Make sure remaining locks get cleared for sanity.
string $lockDir
Global dir for all servers.
const LOCK_SH
Lock types; stronger locks have higher values.
Definition: LockManager.php:68
doSingleLock( $path, $type)
Lock a single resource key.
doUnlock(array $paths, $type)
doSingleUnlock( $path, $type)
Unlock a single resource key.
getLockPath( $path)
Get the path to the lock file for a key.
closeLockHandles( $path, array $handlesToClose)
doLock(array $paths, $type)
array $handles
Map of (locked key => lock file handle)