62 parent::__construct( $config );
64 $this->lockServers = $config[
'lockServers'];
65 if ( isset( $config[
'srvsByBucket'] ) ) {
67 $this->srvsByBucket = array_filter( $config[
'srvsByBucket'],
'is_array' );
68 $this->srvsByBucket = array_values( $this->srvsByBucket );
70 $this->srvsByBucket = [ array_keys( $this->lockServers ) ];
73 $config[
'redisConfig'][
'serializer'] =
'none';
80 $pathList = array_merge( ...array_values( $pathsByType ) );
82 $server = $this->lockServers[$lockSrv];
83 $conn = $this->redisPool->getConnection( $server, $this->logger );
85 foreach ( $pathList as
$path ) {
86 $status->fatal(
'lockmanager-fail-acquirelock',
$path );
93 foreach ( $pathsByType as $type => $paths ) {
95 foreach ( $paths as
$path ) {
105 -- Load input params (e.g. session, ttl, time of request)
106 local rSession, rTTL, rMaxTTL, rTime = unpack(ARGV)
107 -- Check that all the locks can be acquired
108 for i,requestKey in ipairs(KEYS)
do
109 local _, _, rType, resourceKey =
string.find(requestKey,
"(%w+):(%w+)$")
110 local keyIsFree =
true
111 local currentLocks = redis.call(
'hKeys',resourceKey)
112 for i,lockKey in ipairs(currentLocks)
do
113 -- Get the type and session of
this lock
114 local _, _, type, session =
string.find(lockKey,
"(%w+):(%w+)")
115 -- Check any locks that are not owned by
this session
116 if session ~= rSession then
117 local lockExpiry = redis.call(
'hGet',resourceKey,lockKey)
118 if 1*lockExpiry < 1*rTime then
119 -- Lock is stale, so just prune it out
120 redis.call(
'hDel',resourceKey,lockKey)
121 elseif rType ==
'EX' or type ==
'EX' then
127 if not keyIsFree then
128 failed[#failed+1] = requestKey
131 -- If all locks could be acquired, then
do so
133 for i,requestKey in ipairs(KEYS)
do
134 local _, _, rType, resourceKey =
string.find(requestKey,
"(%w+):(%w+)$")
135 redis.call(
'hSet',resourceKey,rType ..
':' .. rSession,rTime + rTTL)
136 -- In addition to invalidation logic, be sure to garbage collect
137 redis.call(
'expire',resourceKey,rMaxTTL)
142 $res = $conn->luaEval( $script,
144 array_keys( $pathsByKey ),
152 count( $pathsByKey ) # number of first argument(s) that are keys
154 }
catch ( RedisException $e ) {
156 $this->redisPool->handleError( $conn, $e );
159 if ( $res ===
false ) {
160 foreach ( $pathList as
$path ) {
161 $status->fatal(
'lockmanager-fail-acquirelock',
$path );
163 } elseif ( count( $res ) ) {
164 $status->fatal(
'lockmanager-fail-conflict' );
173 $pathList = array_merge( ...array_values( $pathsByType ) );
175 $server = $this->lockServers[$lockSrv];
176 $conn = $this->redisPool->getConnection( $server, $this->logger );
178 foreach ( $pathList as
$path ) {
179 $status->fatal(
'lockmanager-fail-releaselock',
$path );
186 foreach ( $pathsByType as $type => $paths ) {
188 foreach ( $paths as
$path ) {
198 -- Load input params (e.g. session)
199 local rSession = unpack(ARGV)
200 for i,requestKey in ipairs(KEYS)
do
201 local _, _, rType, resourceKey =
string.find(requestKey,
"(%w+):(%w+)$")
202 local released = redis.call(
'hDel',resourceKey,rType ..
':' .. rSession)
204 -- Remove the whole structure
if it is now empty
205 if redis.call(
'hLen',resourceKey) == 0 then
206 redis.call(
'del',resourceKey)
209 failed[#failed+1] = requestKey
214 $res = $conn->luaEval( $script,
216 array_keys( $pathsByKey ),
221 count( $pathsByKey ) # number of first argument(s) that are keys
223 }
catch ( RedisException $e ) {
225 $this->redisPool->handleError( $conn, $e );
228 if ( $res ===
false ) {
229 foreach ( $pathList as
$path ) {
230 $status->fatal(
'lockmanager-fail-releaselock',
$path );
233 foreach ( $res as $key ) {
234 $status->fatal(
'lockmanager-fail-releaselock', $pathsByKey[$key] );
246 $conn = $this->redisPool->getConnection( $this->lockServers[$lockSrv], $this->logger );
265 while ( count( $this->locksHeld ) ) {
267 foreach ( $this->locksHeld as
$path => $locks ) {
268 foreach ( $locks as $type => $count ) {
269 $pathsByType[$type][] =
$path;
lock(array $paths, $type=self::LOCK_EX, $timeout=0)
Lock the resources at the given abstract paths.
const LOCK_SH
Lock types; stronger locks have higher values.
sha1Base36Absolute( $path)
Get the base 36 SHA-1 of a string, padded to 31 digits.
unlockByType(array $pathsByType)
Unlock the resources at the given abstract paths.
Base class for lock managers that use a quorum of peer servers for locks.
static singleton(array $options)
Manage locks using redis servers.
__construct(array $config)
Construct a new instance from configuration.
recordKeyForPath( $path, $type)
freeLocksOnServer( $lockSrv, array $pathsByType)
Get a connection to a lock server and release locks on $paths.
RedisConnectionPool $redisPool
getLocksOnServer( $lockSrv, array $pathsByType)
Get a connection to a lock server and acquire locks.
array $lockServers
Map server names to hostname/IP and port numbers.
releaseAllLocks()
Release all locks that this session is holding.
__destruct()
Make sure remaining locks get cleared.
array $lockTypeMap
Mapping of lock types to the type actually used.
isServerUp( $lockSrv)
Check if a lock server is up.
static newGood( $value=null)
Factory function for good results.