Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1428864
FSLockManager.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
FSLockManager.php
View Options
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* Simple lock management based on server-local temporary files.
*
* All locks are non-blocking, which avoids deadlocks.
*
* This should work fine for small sites running from a single web server.
* Do not use this with 'lockDirectory' set to an NFS mount unless the
* NFS client is at least version 2.6.12. Otherwise, the BSD flock()
* locks will be ignored; see http://nfs.sourceforge.net/#section_d.
*
* @ingroup LockManager
* @since 1.19
*/
class
FSLockManager
extends
LockManager
{
/** @var array Mapping of lock types to the type actually used */
protected
$lockTypeMap
=
[
self
::
LOCK_SH
=>
self
::
LOCK_SH
,
self
::
LOCK_UW
=>
self
::
LOCK_SH
,
self
::
LOCK_EX
=>
self
::
LOCK_EX
];
/** @var string Global dir for all servers */
protected
$lockDir
;
/** @var array Map of (locked key => lock file handle) */
protected
$handles
=
[];
/** @var bool */
protected
$isWindows
;
/**
* Construct a new instance from configuration.
*
* @param array $config Includes:
* - lockDirectory : Directory containing the lock files
*/
public
function
__construct
(
array
$config
)
{
parent
::
__construct
(
$config
);
$this
->
lockDir
=
$config
[
'lockDirectory'
];
$this
->
isWindows
=
(
PHP_OS_FAMILY
===
'Windows'
);
}
/**
* @see LockManager::doLock()
* @param array $paths
* @param int $type
* @return StatusValue
*/
protected
function
doLock
(
array
$paths
,
$type
)
{
$status
=
StatusValue
::
newGood
();
$lockedPaths
=
[];
// files locked in this attempt
foreach
(
$paths
as
$path
)
{
$status
->
merge
(
$this
->
doSingleLock
(
$path
,
$type
)
);
if
(
$status
->
isOK
()
)
{
$lockedPaths
[]
=
$path
;
}
else
{
// Abort and unlock everything
$status
->
merge
(
$this
->
doUnlock
(
$lockedPaths
,
$type
)
);
return
$status
;
}
}
return
$status
;
}
/**
* @see LockManager::doUnlock()
* @param array $paths
* @param int $type
* @return StatusValue
*/
protected
function
doUnlock
(
array
$paths
,
$type
)
{
$status
=
StatusValue
::
newGood
();
foreach
(
$paths
as
$path
)
{
$status
->
merge
(
$this
->
doSingleUnlock
(
$path
,
$type
)
);
}
return
$status
;
}
/**
* Lock a single resource key
*
* @param string $path
* @param int $type
* @return StatusValue
*/
protected
function
doSingleLock
(
$path
,
$type
)
{
$status
=
StatusValue
::
newGood
();
if
(
isset
(
$this
->
locksHeld
[
$path
][
$type
]
)
)
{
++
$this
->
locksHeld
[
$path
][
$type
];
}
elseif
(
isset
(
$this
->
locksHeld
[
$path
][
self
::
LOCK_EX
]
)
)
{
$this
->
locksHeld
[
$path
][
$type
]
=
1
;
}
else
{
if
(
isset
(
$this
->
handles
[
$path
]
)
)
{
$handle
=
$this
->
handles
[
$path
];
}
else
{
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$handle
=
@
fopen
(
$this
->
getLockPath
(
$path
),
'a+'
);
if
(
!
$handle
&&
!
is_dir
(
$this
->
lockDir
)
)
{
// Create the lock directory and try again
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
if
(
@
mkdir
(
$this
->
lockDir
,
0777
,
true
)
)
{
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$handle
=
@
fopen
(
$this
->
getLockPath
(
$path
),
'a+'
);
}
else
{
$this
->
logger
->
error
(
"Cannot create directory '{$this->lockDir}'."
);
}
}
}
if
(
$handle
)
{
// Either a shared or exclusive lock
$lock
=
(
$type
==
self
::
LOCK_SH
)
?
LOCK_SH
:
LOCK_EX
;
if
(
flock
(
$handle
,
$lock
|
LOCK_NB
)
)
{
// Record this lock as active
$this
->
locksHeld
[
$path
][
$type
]
=
1
;
$this
->
handles
[
$path
]
=
$handle
;
}
else
{
fclose
(
$handle
);
$status
->
fatal
(
'lockmanager-fail-conflict'
);
}
}
else
{
$status
->
fatal
(
'lockmanager-fail-openlock'
,
$path
);
}
}
return
$status
;
}
/**
* Unlock a single resource key
*
* @param string $path
* @param int $type
* @return StatusValue
*/
protected
function
doSingleUnlock
(
$path
,
$type
)
{
$status
=
StatusValue
::
newGood
();
if
(
!
isset
(
$this
->
locksHeld
[
$path
]
)
)
{
$status
->
warning
(
'lockmanager-notlocked'
,
$path
);
}
elseif
(
!
isset
(
$this
->
locksHeld
[
$path
][
$type
]
)
)
{
$status
->
warning
(
'lockmanager-notlocked'
,
$path
);
}
else
{
$handlesToClose
=
[];
--
$this
->
locksHeld
[
$path
][
$type
];
if
(
$this
->
locksHeld
[
$path
][
$type
]
<=
0
)
{
unset
(
$this
->
locksHeld
[
$path
][
$type
]
);
}
if
(
$this
->
locksHeld
[
$path
]
===
[]
)
{
unset
(
$this
->
locksHeld
[
$path
]
);
// no locks on this path
if
(
isset
(
$this
->
handles
[
$path
]
)
)
{
$handlesToClose
[]
=
$this
->
handles
[
$path
];
unset
(
$this
->
handles
[
$path
]
);
}
}
// Unlock handles to release locks and delete
// any lock files that end up with no locks on them...
if
(
$this
->
isWindows
)
{
// Windows: for any process, including this one,
// calling unlink() on a locked file will fail
$status
->
merge
(
$this
->
closeLockHandles
(
$path
,
$handlesToClose
)
);
$status
->
merge
(
$this
->
pruneKeyLockFiles
(
$path
)
);
}
else
{
// Unix: unlink() can be used on files currently open by this
// process and we must do so in order to avoid race conditions
$status
->
merge
(
$this
->
pruneKeyLockFiles
(
$path
)
);
$status
->
merge
(
$this
->
closeLockHandles
(
$path
,
$handlesToClose
)
);
}
}
return
$status
;
}
/**
* @param string $path
* @param array $handlesToClose
* @return StatusValue
*/
private
function
closeLockHandles
(
$path
,
array
$handlesToClose
)
{
$status
=
StatusValue
::
newGood
();
foreach
(
$handlesToClose
as
$handle
)
{
if
(
!
flock
(
$handle
,
LOCK_UN
)
)
{
$status
->
fatal
(
'lockmanager-fail-releaselock'
,
$path
);
}
if
(
!
fclose
(
$handle
)
)
{
$status
->
warning
(
'lockmanager-fail-closelock'
,
$path
);
}
}
return
$status
;
}
/**
* @param string $path
* @return StatusValue
*/
private
function
pruneKeyLockFiles
(
$path
)
{
$status
=
StatusValue
::
newGood
();
if
(
!
isset
(
$this
->
locksHeld
[
$path
]
)
)
{
# No locks are held for the lock file anymore
if
(
!
unlink
(
$this
->
getLockPath
(
$path
)
)
)
{
$status
->
warning
(
'lockmanager-fail-deletelock'
,
$path
);
}
unset
(
$this
->
handles
[
$path
]
);
}
return
$status
;
}
/**
* Get the path to the lock file for a key
* @param string $path
* @return string
*/
protected
function
getLockPath
(
$path
)
{
return
"{$this->lockDir}/{$this->sha1Base36Absolute( $path )}.lock"
;
}
/**
* Make sure remaining locks get cleared
*/
public
function
__destruct
()
{
while
(
count
(
$this
->
locksHeld
)
)
{
foreach
(
$this
->
locksHeld
as
$path
=>
$locks
)
{
$this
->
doSingleUnlock
(
$path
,
self
::
LOCK_EX
);
$this
->
doSingleUnlock
(
$path
,
self
::
LOCK_SH
);
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 16:46 (11 h, 52 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
39/9f/dc082093452d946de65771f9a47f
Default Alt Text
FSLockManager.php (7 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment