Mutex.php
5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use Yii;
use yii\base\InvalidConfigException;
use yii\di\Instance;
/**
* Redis Mutex implements a mutex component using [redis](http://redis.io/) as the storage medium.
*
* Redis Mutex requires redis version 2.6.12 or higher to work properly.
*
* It needs to be configured with a redis [[Connection]] that is also configured as an application component.
* By default it will use the `redis` application component.
*
* To use redis Mutex as the application component, configure the application as follows:
*
* ~~~
* [
* 'components' => [
* 'mutex' => [
* 'class' => 'yii\redis\Mutex',
* 'redis' => [
* 'hostname' => 'localhost',
* 'port' => 6379,
* 'database' => 0,
* ]
* ],
* ],
* ]
* ~~~
*
* Or if you have configured the redis [[Connection]] as an application component, the following is sufficient:
*
* ~~~
* [
* 'components' => [
* 'mutex' => [
* 'class' => 'yii\redis\Mutex',
* // 'redis' => 'redis' // id of the connection application component
* ],
* ],
* ]
* ~~~
*
* @see \yii\mutex\Mutex
* @see http://redis.io/topics/distlock
*
* @author Sergey Makinen <sergey@makinen.ru>
* @author Alexander Zhuravlev <axelhex@gmail.com>
* @since 2.0.6
*/
class Mutex extends \yii\mutex\Mutex
{
/**
* @var int the number of seconds in which the lock will be auto released.
*/
public $expire = 30;
/**
* @var string a string prefixed to every cache key so that it is unique. If not set,
* it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
* if you don't want to use key prefix. It is recommended that you explicitly set this property to some
* static value if the cached data needs to be shared among multiple applications.
*/
public $keyPrefix;
/**
* @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
* This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
* redis connection as an application component.
* After the Mutex object is created, if you want to change this property, you should only assign it
* with a Redis [[Connection]] object.
*/
public $redis = 'redis';
/**
* @var array Redis lock values. Used to be safe that only a lock owner can release it.
*/
private $_lockValues = [];
/**
* Initializes the redis Mutex component.
* This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
* @throws InvalidConfigException if [[redis]] is invalid.
*/
public function init()
{
parent::init();
$this->redis = Instance::ensure($this->redis, Connection::className());
if ($this->keyPrefix === null) {
$this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
}
}
/**
* Acquires a lock by name.
* @param string $name of the lock to be acquired. Must be unique.
* @param int $timeout time to wait for lock to be released. Defaults to `0` meaning that method will return
* false immediately in case lock was already acquired.
* @return bool lock acquiring result.
*/
protected function acquireLock($name, $timeout = 0)
{
$key = $this->calculateKey($name);
$value = Yii::$app->security->generateRandomString(20);
$waitTime = 0;
while (!$this->redis->executeCommand('SET', [$key, $value, 'NX', 'PX', (int) ($this->expire * 1000)])) {
$waitTime++;
if ($waitTime > $timeout) {
return false;
}
sleep(1);
}
$this->_lockValues[$name] = $value;
return true;
}
/**
* Releases acquired lock. This method will return `false` in case the lock was not found or Redis command failed.
* @param string $name of the lock to be released. This lock must already exist.
* @return bool lock release result: `false` in case named lock was not found or Redis command failed.
*/
protected function releaseLock($name)
{
static $releaseLuaScript = <<<LUA
if redis.call("GET",KEYS[1])==ARGV[1] then
return redis.call("DEL",KEYS[1])
else
return 0
end
LUA;
if (!isset($this->_lockValues[$name]) || !$this->redis->executeCommand('EVAL', [
$releaseLuaScript,
1,
$this->calculateKey($name),
$this->_lockValues[$name]
])) {
return false;
} else {
unset($this->_lockValues[$name]);
return true;
}
}
/**
* Generates a unique key used for storing the mutex in Redis.
* @param string $name mutex name.
* @return string a safe cache key associated with the mutex name.
*/
protected function calculateKey($name)
{
return $this->keyPrefix . md5(json_encode([__CLASS__, $name]));
}
}