e34f07558c25ffd413a0eee665e90a0b4e2737b0
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Locking / SimpleLockStrategy.php
1 <?php
2 namespace TYPO3\CMS\Core\Locking;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
18 use TYPO3\CMS\Core\Locking\Exception\LockCreateException;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * Simple file locking
23 */
24 class SimpleLockStrategy implements LockingStrategyInterface
25 {
26 const FILE_LOCK_FOLDER = 'typo3temp/var/locks/';
27
28 /**
29 * @var string File path used for this lock
30 */
31 protected $filePath;
32
33 /**
34 * @var bool True if lock is acquired
35 */
36 protected $isAcquired = false;
37
38 /**
39 * @var int Number of times a locked resource is tried to be acquired. Only used in manual locks method "simple".
40 */
41 protected $loops = 150;
42
43 /**
44 * @var int Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock. Only used in manual lock method "simple".
45 */
46 protected $step = 200;
47
48 /**
49 * @param string $subject ID to identify this lock in the system
50 * @throws LockCreateException if the lock could not be created
51 */
52 public function __construct($subject)
53 {
54 // Tests if the directory for simple locks is available.
55 // If not, the directory will be created. The lock path is usually
56 // below typo3temp/var, typo3temp/var itself should exist already
57 $path = PATH_site . self::FILE_LOCK_FOLDER;
58 if (!is_dir($path)) {
59 // Not using mkdir_deep on purpose here, if typo3temp/var itself
60 // does not exist, this issue should be solved on a different
61 // level of the application.
62 if (!GeneralUtility::mkdir($path)) {
63 throw new LockCreateException('Cannot create directory ' . $path, 1395140007);
64 }
65 }
66 if (!is_writable($path)) {
67 throw new LockCreateException('Cannot write to directory ' . $path, 1396278700);
68 }
69 $this->filePath = $path . 'simple_' . md5((string)$subject);
70 }
71
72 /**
73 * @param int $loops Number of times a locked resource is tried to be acquired.
74 * @param int $step Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock.
75 * @return void
76 */
77 public function init($loops = 0, $step = 0)
78 {
79 $this->loops = (int)$loops;
80 $this->step = (int)$step;
81 }
82
83 /**
84 * Destructor:
85 * Releases lock automatically when instance is destroyed and release resources
86 */
87 public function __destruct()
88 {
89 $this->release();
90 }
91
92 /**
93 * Release the lock
94 *
95 * @return bool Returns TRUE on success or FALSE on failure
96 */
97 public function release()
98 {
99 if (!$this->isAcquired) {
100 return true;
101 }
102
103 $success = true;
104 if (
105 GeneralUtility::isAllowedAbsPath($this->filePath)
106 && GeneralUtility::isFirstPartOfStr($this->filePath, PATH_site . self::FILE_LOCK_FOLDER)
107 ) {
108 if (@unlink($this->filePath) === false) {
109 $success = false;
110 }
111 }
112
113 $this->isAcquired = false;
114 return $success;
115 }
116
117 /**
118 * Get status of this lock
119 *
120 * @return bool Returns TRUE if lock is acquired by this locker, FALSE otherwise
121 */
122 public function isAcquired()
123 {
124 return $this->isAcquired;
125 }
126
127 /**
128 * @return int LOCK_CAPABILITY_* elements combined with bit-wise OR
129 */
130 public static function getCapabilities()
131 {
132 return self::LOCK_CAPABILITY_EXCLUSIVE | self::LOCK_CAPABILITY_NOBLOCK;
133 }
134
135 /**
136 * Try to acquire a lock
137 *
138 * @param int $mode LOCK_CAPABILITY_EXCLUSIVE or self::LOCK_CAPABILITY_NOBLOCK
139 * @return bool Returns TRUE if the lock was acquired successfully
140 * @throws LockAcquireWouldBlockException
141 */
142 public function acquire($mode = self::LOCK_CAPABILITY_EXCLUSIVE)
143 {
144 if ($this->isAcquired) {
145 return true;
146 }
147
148 if (file_exists($this->filePath)) {
149 $maxExecutionTime = (int)ini_get('max_execution_time');
150 $maxAge = time() - ($maxExecutionTime ?: 120);
151 if (@filectime($this->filePath) < $maxAge) {
152 // Remove stale lock file
153 @unlink($this->filePath);
154 }
155 }
156
157 $this->isAcquired = false;
158 $wouldBlock = false;
159 for ($i = 0; $i < $this->loops; $i++) {
160 $filePointer = @fopen($this->filePath, 'x');
161 if ($filePointer !== false) {
162 fclose($filePointer);
163 GeneralUtility::fixPermissions($this->filePath);
164 $this->isAcquired = true;
165 break;
166 }
167 if ($mode & self::LOCK_CAPABILITY_NOBLOCK) {
168 $wouldBlock = true;
169 break;
170 }
171 usleep($this->step * 1000);
172 }
173
174 if ($mode & self::LOCK_CAPABILITY_NOBLOCK && !$this->isAcquired && $wouldBlock) {
175 throw new LockAcquireWouldBlockException('Failed to acquire lock because the request would block.', 1428700748);
176 }
177
178 return $this->isAcquired;
179 }
180
181 /**
182 * @return int Returns a priority for the method. 0 to 100, 100 is highest
183 */
184 public static function getPriority()
185 {
186 return 50;
187 }
188
189 /**
190 * Destroys the resource associated with the lock
191 *
192 * @return void
193 */
194 public function destroy()
195 {
196 @unlink($this->filePath);
197 }
198 }