1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @link http://www.yiiframework.com/ |
4
|
|
|
* @copyright Copyright (c) 2008 Yii Software LLC |
5
|
|
|
* @license http://www.yiiframework.com/license/ |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace yii\mutex; |
9
|
|
|
|
10
|
|
|
use Yii; |
11
|
|
|
use yii\base\InvalidConfigException; |
12
|
|
|
use yii\helpers\FileHelper; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* FileMutex implements mutex "lock" mechanism via local file system files. |
16
|
|
|
* This component relies on PHP `flock()` function. |
17
|
|
|
* |
18
|
|
|
* Application configuration example: |
19
|
|
|
* |
20
|
|
|
* ```php |
21
|
|
|
* [ |
22
|
|
|
* 'components' => [ |
23
|
|
|
* 'mutex' => [ |
24
|
|
|
* 'class' => 'yii\mutex\FileMutex' |
25
|
|
|
* ], |
26
|
|
|
* ], |
27
|
|
|
* ] |
28
|
|
|
* ``` |
29
|
|
|
* |
30
|
|
|
* Note: this component can maintain the locks only for the single web server, |
31
|
|
|
* it probably will not suffice to your in case you are using cloud server solution. |
32
|
|
|
* |
33
|
|
|
* Warning: due to `flock()` function nature this component is unreliable when |
34
|
|
|
* using a multithreaded server API like ISAPI. |
35
|
|
|
* |
36
|
|
|
* @see Mutex |
37
|
|
|
* |
38
|
|
|
* @author resurtm <[email protected]> |
39
|
|
|
* @since 2.0 |
40
|
|
|
*/ |
41
|
|
|
class FileMutex extends Mutex |
42
|
|
|
{ |
43
|
|
|
/** |
44
|
|
|
* @var string the directory to store mutex files. You may use [path alias](guide:concept-aliases) here. |
45
|
|
|
* Defaults to the "mutex" subdirectory under the application runtime path. |
46
|
|
|
*/ |
47
|
|
|
public $mutexPath = '@runtime/mutex'; |
48
|
|
|
/** |
49
|
|
|
* @var int the permission to be set for newly created mutex files. |
50
|
|
|
* This value will be used by PHP chmod() function. No umask will be applied. |
51
|
|
|
* If not set, the permission will be determined by the current environment. |
52
|
|
|
*/ |
53
|
|
|
public $fileMode; |
54
|
|
|
/** |
55
|
|
|
* @var int the permission to be set for newly created directories. |
56
|
|
|
* This value will be used by PHP chmod() function. No umask will be applied. |
57
|
|
|
* Defaults to 0775, meaning the directory is read-writable by owner and group, |
58
|
|
|
* but read-only for other users. |
59
|
|
|
*/ |
60
|
|
|
public $dirMode = 0775; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var resource[] stores all opened lock files. Keys are lock names and values are file handles. |
64
|
|
|
*/ |
65
|
|
|
private $_files = []; |
66
|
|
|
|
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Initializes mutex component implementation dedicated for UNIX, GNU/Linux, Mac OS X, and other UNIX-like |
70
|
|
|
* operating systems. |
71
|
|
|
* @throws InvalidConfigException |
72
|
|
|
*/ |
73
|
3 |
|
public function init() |
74
|
|
|
{ |
75
|
3 |
|
parent::init(); |
76
|
3 |
|
$this->mutexPath = Yii::getAlias($this->mutexPath); |
|
|
|
|
77
|
3 |
|
if (!is_dir($this->mutexPath)) { |
78
|
1 |
|
FileHelper::createDirectory($this->mutexPath, $this->dirMode, true); |
79
|
|
|
} |
80
|
3 |
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Acquires lock by given name. |
84
|
|
|
* @param string $name of the lock to be acquired. |
85
|
|
|
* @param int $timeout to wait for lock to become released. |
86
|
|
|
* @return bool acquiring result. |
87
|
|
|
*/ |
88
|
3 |
|
protected function acquireLock($name, $timeout = 0) |
89
|
|
|
{ |
90
|
3 |
|
$filePath = $this->getLockFilePath($name); |
91
|
3 |
|
$waitTime = 0; |
92
|
|
|
|
93
|
3 |
|
while (true) { |
94
|
3 |
|
$file = fopen($filePath, 'w+'); |
95
|
|
|
|
96
|
3 |
|
if ($file === false) { |
97
|
|
|
return false; |
98
|
|
|
} |
99
|
|
|
|
100
|
3 |
|
if ($this->fileMode !== null) { |
101
|
|
|
@chmod($filePath, $this->fileMode); |
|
|
|
|
102
|
|
|
} |
103
|
|
|
|
104
|
3 |
|
if (!flock($file, LOCK_EX | LOCK_NB)) { |
105
|
1 |
|
fclose($file); |
106
|
|
|
|
107
|
1 |
|
if (++$waitTime > $timeout) { |
108
|
1 |
|
return false; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
sleep(1); |
112
|
|
|
continue; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// Under unix we delete the lock file before releasing the related handle. Thus it's possible that we've acquired a lock on |
116
|
|
|
// a non-existing file here (race condition). We must compare the inode of the lock file handle with the inode of the actual lock file. |
117
|
|
|
// If they do not match we simply continue the loop since we can assume the inodes will be equal on the next try. |
118
|
|
|
// Example of race condition without inode-comparison: |
119
|
|
|
// Script A: locks file |
120
|
|
|
// Script B: opens file |
121
|
|
|
// Script A: unlinks and unlocks file |
122
|
|
|
// Script B: locks handle of *unlinked* file |
123
|
|
|
// Script C: opens and locks *new* file |
124
|
|
|
// In this case we would have acquired two locks for the same file path. |
125
|
3 |
|
if (DIRECTORY_SEPARATOR !== '\\' && fstat($file)['ino'] !== @fileinode($filePath)) { |
126
|
|
|
clearstatcache(true, $filePath); |
127
|
|
|
flock($file, LOCK_UN); |
128
|
|
|
fclose($file); |
129
|
|
|
continue; |
130
|
|
|
} |
131
|
|
|
|
132
|
3 |
|
$this->_files[$name] = $file; |
133
|
3 |
|
return true; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
// Should not be reached normally. |
137
|
|
|
return false; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Releases lock by given name. |
142
|
|
|
* @param string $name of the lock to be released. |
143
|
|
|
* @return bool release result. |
144
|
|
|
*/ |
145
|
3 |
|
protected function releaseLock($name) |
146
|
|
|
{ |
147
|
3 |
|
if (!isset($this->_files[$name])) { |
148
|
|
|
return false; |
149
|
|
|
} |
150
|
|
|
|
151
|
3 |
|
if (DIRECTORY_SEPARATOR === '\\') { |
152
|
|
|
// Under windows it's not possible to delete a file opened via fopen (either by own or other process). |
153
|
|
|
// That's why we must first unlock and close the handle and then *try* to delete the lock file. |
154
|
|
|
flock($this->_files[$name], LOCK_UN); |
155
|
|
|
fclose($this->_files[$name]); |
156
|
|
|
@unlink($this->getLockFilePath($name)); |
|
|
|
|
157
|
|
|
} else { |
158
|
|
|
// Under unix it's possible to delete a file opened via fopen (either by own or other process). |
159
|
|
|
// That's why we must unlink (the currently locked) lock file first and then unlock and close the handle. |
160
|
3 |
|
unlink($this->getLockFilePath($name)); |
161
|
3 |
|
flock($this->_files[$name], LOCK_UN); |
162
|
3 |
|
fclose($this->_files[$name]); |
163
|
|
|
} |
164
|
|
|
|
165
|
3 |
|
unset($this->_files[$name]); |
166
|
3 |
|
return true; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Generate path for lock file. |
171
|
|
|
* @param string $name |
172
|
|
|
* @return string |
173
|
|
|
* @since 2.0.10 |
174
|
|
|
*/ |
175
|
3 |
|
protected function getLockFilePath($name) |
176
|
|
|
{ |
177
|
3 |
|
return $this->mutexPath . DIRECTORY_SEPARATOR . md5($name) . '.lock'; |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.