36 protected $lockTypeMap = [
37 self::LOCK_SH => self::LOCK_SH,
38 self::LOCK_UW => self::LOCK_SH,
39 self::LOCK_EX => self::LOCK_EX
46 protected $handles = [];
58 parent::__construct( $config );
60 $this->lockDir = $config[
'lockDirectory'];
61 $this->isWindows = ( PHP_OS_FAMILY ===
'Windows' );
70 protected function doLock( array $paths, $type ) {
71 $status = StatusValue::newGood();
74 foreach ( $paths as
$path ) {
76 if ( $status->isOK() ) {
77 $lockedPaths[] =
$path;
80 $status->merge( $this->
doUnlock( $lockedPaths, $type ) );
95 protected function doUnlock( array $paths, $type ) {
96 $status = StatusValue::newGood();
98 foreach ( $paths as
$path ) {
113 $status = StatusValue::newGood();
115 if ( isset( $this->locksHeld[
$path][$type] ) ) {
116 ++$this->locksHeld[
$path][$type];
117 } elseif ( isset( $this->locksHeld[
$path][self::LOCK_EX] ) ) {
118 $this->locksHeld[
$path][$type] = 1;
120 if ( isset( $this->handles[
$path] ) ) {
121 $handle = $this->handles[
$path];
125 if ( !$handle && !is_dir( $this->lockDir ) ) {
128 if ( @mkdir( $this->lockDir, 0777,
true ) ) {
132 $this->logger->error(
"Cannot create directory '{$this->lockDir}'." );
139 if ( flock( $handle, $lock | LOCK_NB ) ) {
141 $this->locksHeld[
$path][$type] = 1;
142 $this->handles[
$path] = $handle;
145 $status->fatal(
'lockmanager-fail-conflict' );
148 $status->fatal(
'lockmanager-fail-openlock',
$path );
163 $status = StatusValue::newGood();
165 if ( !isset( $this->locksHeld[
$path] ) ) {
166 $status->warning(
'lockmanager-notlocked',
$path );
167 } elseif ( !isset( $this->locksHeld[
$path][$type] ) ) {
168 $status->warning(
'lockmanager-notlocked',
$path );
170 $handlesToClose = [];
171 --$this->locksHeld[
$path][$type];
172 if ( $this->locksHeld[
$path][$type] <= 0 ) {
173 unset( $this->locksHeld[
$path][$type] );
175 if ( $this->locksHeld[
$path] === [] ) {
176 unset( $this->locksHeld[
$path] );
177 if ( isset( $this->handles[
$path] ) ) {
178 $handlesToClose[] = $this->handles[
$path];
179 unset( $this->handles[
$path] );
184 if ( $this->isWindows ) {
187 $status->merge( $this->closeLockHandles(
$path, $handlesToClose ) );
188 $status->merge( $this->pruneKeyLockFiles(
$path ) );
192 $status->merge( $this->pruneKeyLockFiles(
$path ) );
193 $status->merge( $this->closeLockHandles(
$path, $handlesToClose ) );
205 private function closeLockHandles(
$path, array $handlesToClose ) {
206 $status = StatusValue::newGood();
207 foreach ( $handlesToClose as $handle ) {
208 if ( !flock( $handle, LOCK_UN ) ) {
209 $status->fatal(
'lockmanager-fail-releaselock',
$path );
211 if ( !fclose( $handle ) ) {
212 $status->warning(
'lockmanager-fail-closelock',
$path );
223 private function pruneKeyLockFiles(
$path ) {
225 if ( !isset( $this->locksHeld[
$path] ) ) {
226 # No locks are held for the lock file anymore
228 $status->warning(
'lockmanager-fail-deletelock',
$path );
230 unset( $this->handles[
$path] );
242 return "{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock";
249 while ( count( $this->locksHeld ) ) {
250 foreach ( $this->locksHeld as
$path => $locks ) {
Simple lock management based on server-local temporary files.
doSingleLock( $path, $type)
Lock a single resource key.
string $lockDir
Global dir for all servers.
__destruct()
Make sure remaining locks get cleared.
doLock(array $paths, $type)
array $handles
Map of (locked key => lock file handle)
doSingleUnlock( $path, $type)
Unlock a single resource key.
__construct(array $config)
Construct a new instance from configuration.
getLockPath( $path)
Get the path to the lock file for a key.
array $lockTypeMap
Mapping of lock types to the type actually used.
doUnlock(array $paths, $type)
Resource locking handling.
const LOCK_SH
Lock types; stronger locks have higher values.
static newGood( $value=null)
Factory function for good results.