d108e7700e8263edbe810ac2dd3d1aef11b0ee06
[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 * @author Markus Klein <klein.t3@reelworx.at>
25 */
26 class SimpleLockStrategy implements LockingStrategyInterface {
27
28 const FILE_LOCK_FOLDER = 'typo3temp/locks/';
29
30 /**
31 * @var string File path used for this lock
32 */
33 protected $filePath;
34
35 /**
36 * @var bool True if lock is acquired
37 */
38 protected $isAcquired = FALSE;
39
40 /**
41 * @var int Number of times a locked resource is tried to be acquired. Only used in manual locks method "simple".
42 */
43 protected $loops = 150;
44
45 /**
46 * @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".
47 */
48 protected $step = 200;
49
50 /**
51 * @param string $subject ID to identify this lock in the system
52 * @throws LockCreateException if the lock could not be created
53 */
54 public function __construct($subject) {
55 // Tests if the directory for simple locks is available.
56 // If not, the directory will be created. The lock path is usually
57 // below typo3temp, typo3temp itself should exist already
58 $path = PATH_site . self::FILE_LOCK_FOLDER;
59 if (!is_dir($path)) {
60 // Not using mkdir_deep on purpose here, if typo3temp itself
61 // does not exist, this issue should be solved on a different
62 // level of the application.
63 if (!GeneralUtility::mkdir($path)) {
64 throw new LockCreateException('Cannot create directory ' . $path, 1395140007);
65 }
66 }
67 if (!is_writable($path)) {
68 throw new LockCreateException('Cannot write to directory ' . $path, 1396278700);
69 }
70 $this->filePath = $path . md5((string)$subject);
71 }
72
73 /**
74 * @param int $loops Number of times a locked resource is tried to be acquired.
75 * @param int $step Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock.
76 * @return void
77 */
78 public function init($loops = 0, $step = 0) {
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 $this->release();
89 }
90
91 /**
92 * Release the lock
93 *
94 * @return bool Returns TRUE on success or FALSE on failure
95 */
96 public function release() {
97 if (!$this->isAcquired) {
98 return TRUE;
99 }
100
101 $success = TRUE;
102 if (
103 GeneralUtility::isAllowedAbsPath($this->filePath)
104 && GeneralUtility::isFirstPartOfStr($this->filePath, PATH_site . self::FILE_LOCK_FOLDER)
105 ) {
106 if (@unlink($this->filePath) === FALSE) {
107 $success = FALSE;
108 }
109 }
110
111 $this->isAcquired = FALSE;
112 return $success;
113 }
114
115 /**
116 * Get status of this lock
117 *
118 * @return bool Returns TRUE if lock is acquired by this locker, FALSE otherwise
119 */
120 public function isAcquired() {
121 return $this->isAcquired;
122 }
123
124 /**
125 * @return int LOCK_CAPABILITY_* elements combined with bit-wise OR
126 */
127 static public function getCapabilities() {
128 return self::LOCK_CAPABILITY_EXCLUSIVE | self::LOCK_CAPABILITY_NOBLOCK;
129 }
130
131 /**
132 * Try to acquire a lock
133 *
134 * @param int $mode LOCK_CAPABILITY_EXCLUSIVE or self::LOCK_CAPABILITY_NOBLOCK
135 * @return bool Returns TRUE if the lock was acquired successfully
136 * @throws \RuntimeException with code 1428700748 if the acquire would have blocked and NOBLOCK was set
137 */
138 public function acquire($mode = self::LOCK_CAPABILITY_EXCLUSIVE) {
139 if ($this->isAcquired) {
140 return TRUE;
141 }
142
143 if (file_exists($this->filePath)) {
144 $maxExecutionTime = (int)ini_get('max_execution_time');
145 $maxAge = time() - ($maxExecutionTime ?: 120);
146 if (@filectime($this->filePath) < $maxAge) {
147 // Remove stale lock file
148 @unlink($this->filePath);
149 }
150 }
151
152 $this->isAcquired = FALSE;
153 $wouldBlock = FALSE;
154 for ($i = 0; $i < $this->loops; $i++) {
155 $filePointer = @fopen($this->filePath, 'x');
156 if ($filePointer !== FALSE) {
157 fclose($filePointer);
158 GeneralUtility::fixPermissions($this->filePath);
159 $this->isAcquired = TRUE;
160 break;
161 }
162 if ($mode & self::LOCK_CAPABILITY_NOBLOCK) {
163 $wouldBlock = TRUE;
164 break;
165 }
166 usleep($this->step * 1000);
167 }
168
169 if ($mode & self::LOCK_CAPABILITY_NOBLOCK && !$this->isAcquired && $wouldBlock) {
170 throw new LockAcquireWouldBlockException('Failed to acquire lock because the request would block.', 1428700748);
171 }
172
173 return $this->isAcquired;
174 }
175
176 /**
177 * @return int Returns a priority for the method. 0 to 100, 100 is highest
178 */
179 static public function getPriority() {
180 return 25;
181 }
182
183 }