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 | * |
||||
17 | * This component relies on PHP `flock()` function. |
||||
18 | * |
||||
19 | * Application configuration example: |
||||
20 | * |
||||
21 | * ```php |
||||
22 | * [ |
||||
23 | * 'components' => [ |
||||
24 | * 'mutex' => [ |
||||
25 | * 'class' => 'yii\mutex\FileMutex' |
||||
26 | * ], |
||||
27 | * ], |
||||
28 | * ] |
||||
29 | * ``` |
||||
30 | * |
||||
31 | * > Note: this component can maintain the locks only for the single web server, |
||||
32 | * > it probably will not suffice in case you are using cloud server solution. |
||||
33 | * |
||||
34 | * > Warning: due to `flock()` function nature this component is unreliable when |
||||
35 | * > using a multithreaded server API like ISAPI. |
||||
36 | * |
||||
37 | * @see Mutex |
||||
38 | * |
||||
39 | * @author resurtm <[email protected]> |
||||
40 | * @since 2.0 |
||||
41 | */ |
||||
42 | class FileMutex extends Mutex |
||||
43 | { |
||||
44 | use RetryAcquireTrait; |
||||
45 | |||||
46 | /** |
||||
47 | * @var string the directory to store mutex files. You may use [path alias](guide:concept-aliases) here. |
||||
48 | * Defaults to the "mutex" subdirectory under the application runtime path. |
||||
49 | */ |
||||
50 | public $mutexPath = '@runtime/mutex'; |
||||
51 | /** |
||||
52 | * @var int the permission to be set for newly created mutex files. |
||||
53 | * This value will be used by PHP chmod() function. No umask will be applied. |
||||
54 | * If not set, the permission will be determined by the current environment. |
||||
55 | */ |
||||
56 | public $fileMode; |
||||
57 | /** |
||||
58 | * @var int the permission to be set for newly created directories. |
||||
59 | * This value will be used by PHP chmod() function. No umask will be applied. |
||||
60 | * Defaults to 0775, meaning the directory is read-writable by owner and group, |
||||
61 | * but read-only for other users. |
||||
62 | */ |
||||
63 | public $dirMode = 0775; |
||||
64 | /** |
||||
65 | * @var bool whether file handling should assume a Windows file system. |
||||
66 | * This value will determine how [[releaseLock()]] goes about deleting the lock file. |
||||
67 | * If not set, it will be determined by checking the DIRECTORY_SEPARATOR constant. |
||||
68 | * @since 2.0.16 |
||||
69 | */ |
||||
70 | public $isWindows; |
||||
71 | |||||
72 | /** |
||||
73 | * @var resource[] stores all opened lock files. Keys are lock names and values are file handles. |
||||
74 | */ |
||||
75 | private $_files = []; |
||||
76 | |||||
77 | |||||
78 | /** |
||||
79 | * Initializes mutex component implementation dedicated for UNIX, GNU/Linux, Mac OS X, and other UNIX-like |
||||
80 | * operating systems. |
||||
81 | * @throws InvalidConfigException |
||||
82 | */ |
||||
83 | 16 | public function init() |
|||
84 | { |
||||
85 | 16 | parent::init(); |
|||
86 | 16 | $this->mutexPath = Yii::getAlias($this->mutexPath); |
|||
87 | 16 | if (!is_dir($this->mutexPath)) { |
|||
88 | 1 | FileHelper::createDirectory($this->mutexPath, $this->dirMode, true); |
|||
89 | } |
||||
90 | 16 | if ($this->isWindows === null) { |
|||
91 | 16 | $this->isWindows = DIRECTORY_SEPARATOR === '\\'; |
|||
92 | } |
||||
93 | 16 | } |
|||
94 | |||||
95 | /** |
||||
96 | * Acquires lock by given name. |
||||
97 | * @param string $name of the lock to be acquired. |
||||
98 | * @param int $timeout time (in seconds) to wait for lock to become released. |
||||
99 | * @return bool acquiring result. |
||||
100 | */ |
||||
101 | 16 | protected function acquireLock($name, $timeout = 0) |
|||
102 | { |
||||
103 | 16 | $filePath = $this->getLockFilePath($name); |
|||
104 | 16 | return $this->retryAcquire($timeout, function () use ($filePath, $name) { |
|||
105 | 16 | $file = fopen($filePath, 'w+'); |
|||
106 | 16 | if ($file === false) { |
|||
107 | return false; |
||||
108 | } |
||||
109 | |||||
110 | 16 | if ($this->fileMode !== null) { |
|||
111 | @chmod($filePath, $this->fileMode); |
||||
0 ignored issues
–
show
|
|||||
112 | } |
||||
113 | |||||
114 | 16 | if (!flock($file, LOCK_EX | LOCK_NB)) { |
|||
115 | 4 | fclose($file); |
|||
116 | 4 | return false; |
|||
117 | } |
||||
118 | |||||
119 | // Under unix we delete the lock file before releasing the related handle. Thus it's possible that we've acquired a lock on |
||||
120 | // 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. |
||||
121 | // If they do not match we simply continue the loop since we can assume the inodes will be equal on the next try. |
||||
122 | // Example of race condition without inode-comparison: |
||||
123 | // Script A: locks file |
||||
124 | // Script B: opens file |
||||
125 | // Script A: unlinks and unlocks file |
||||
126 | // Script B: locks handle of *unlinked* file |
||||
127 | // Script C: opens and locks *new* file |
||||
128 | // In this case we would have acquired two locks for the same file path. |
||||
129 | 16 | if (DIRECTORY_SEPARATOR !== '\\' && fstat($file)['ino'] !== @fileinode($filePath)) { |
|||
130 | clearstatcache(true, $filePath); |
||||
131 | flock($file, LOCK_UN); |
||||
132 | fclose($file); |
||||
133 | return false; |
||||
134 | } |
||||
135 | |||||
136 | 16 | $this->_files[$name] = $file; |
|||
137 | 16 | return true; |
|||
138 | 16 | }); |
|||
139 | } |
||||
140 | |||||
141 | /** |
||||
142 | * Releases lock by given name. |
||||
143 | * @param string $name of the lock to be released. |
||||
144 | * @return bool release result. |
||||
145 | */ |
||||
146 | 16 | protected function releaseLock($name) |
|||
147 | { |
||||
148 | 16 | if (!isset($this->_files[$name])) { |
|||
149 | 7 | return false; |
|||
150 | } |
||||
151 | |||||
152 | 16 | if ($this->isWindows) { |
|||
153 | // Under windows it's not possible to delete a file opened via fopen (either by own or other process). |
||||
154 | // That's why we must first unlock and close the handle and then *try* to delete the lock file. |
||||
155 | flock($this->_files[$name], LOCK_UN); |
||||
156 | fclose($this->_files[$name]); |
||||
157 | @unlink($this->getLockFilePath($name)); |
||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
unlink() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
|
|||||
158 | } else { |
||||
159 | // Under unix it's possible to delete a file opened via fopen (either by own or other process). |
||||
160 | // That's why we must unlink (the currently locked) lock file first and then unlock and close the handle. |
||||
161 | 16 | unlink($this->getLockFilePath($name)); |
|||
162 | 16 | flock($this->_files[$name], LOCK_UN); |
|||
163 | 16 | fclose($this->_files[$name]); |
|||
164 | } |
||||
165 | |||||
166 | 16 | unset($this->_files[$name]); |
|||
167 | 16 | return true; |
|||
168 | } |
||||
169 | |||||
170 | /** |
||||
171 | * Generate path for lock file. |
||||
172 | * @param string $name |
||||
173 | * @return string |
||||
174 | * @since 2.0.10 |
||||
175 | */ |
||||
176 | 16 | protected function getLockFilePath($name) |
|||
177 | { |
||||
178 | 16 | return $this->mutexPath . DIRECTORY_SEPARATOR . md5($name) . '.lock'; |
|||
179 | } |
||||
180 | } |
||||
181 |
If you suppress an error, we recommend checking for the error condition explicitly: