Fixed issue #17284: Formprotection persistToken method is called too often, causing...
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_lock.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2011 Michael Stucki (michael@typo3.org)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Class for providing locking features in TYPO3
29 *
30 * $Id$
31 *
32 * @author Michael Stucki <michael@typo3.org>
33 */
34
35
36 /**
37 * TYPO3 locking class
38 * This class provides an abstract layer to various locking features for TYPO3
39 *
40 * It is intended to blocks requests until some data has been generated.
41 * This is especially useful if two clients are requesting the same website short after each other. While the request of client 1 triggers building and caching of the website, client 2 will be waiting at this lock.
42 *
43 * @author Michael Stucki <michael@typo3.org>
44 * @package TYPO3
45 * @subpackage t3lib
46 * @see class.t3lib_tstemplate.php, class.tslib_fe.php
47 */
48 class t3lib_lock {
49 protected $method;
50 protected $id; // Identifier used for this lock
51 protected $resource; // Resource used for this lock (can be a file or a semaphore resource)
52 protected $filepointer;
53 protected $isAcquired = FALSE;
54
55 protected $loops = 150; // Number of times a locked resource is tried to be acquired. This is only used by manual locks like the "simple" method.
56 protected $step = 200; // Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock. Only used by manual locks like the "simple" method.
57
58
59 /**
60 * Constructor:
61 * initializes locking, check input parameters and set variables accordingly.
62 *
63 * @param string ID to identify this lock in the system
64 * @param string Define which locking method to use. Defaults to "simple".
65 * @param integer Number of times a locked resource is tried to be acquired. This is only used by manual locks like the "simple" method.
66 * @param integer Milliseconds after lock acquire is retried. $loops * $step results in the maximum delay of a lock. Only used by manual locks like the "simple" method.
67 * @return boolean Returns true unless something went wrong
68 */
69 public function __construct($id, $method = '', $loops = 0, $step = 0) {
70
71 // Input checks
72 $id = (string) $id; // Force ID to be string
73 if (intval($loops)) {
74 $this->loops = intval($loops);
75 }
76 if (intval($step)) {
77 $this->step = intval($step);
78 }
79
80 // Detect locking method
81 if (in_array($method, array('disable', 'simple', 'flock', 'semaphore'))) {
82 $this->method = $method;
83 } else {
84 throw new Exception('No such method "' . $method . '"');
85 }
86
87 $success = FALSE;
88 switch ($this->method) {
89 case 'simple':
90 case 'flock':
91 $path = PATH_site . 'typo3temp/locks/';
92 if (!is_dir($path)) {
93 t3lib_div::mkdir($path);
94 }
95 $this->id = md5($id);
96 $this->resource = $path . $this->id;
97 $success = TRUE;
98 break;
99 case 'semaphore':
100 $this->id = abs(crc32($id));
101 if (($this->resource = sem_get($this->id, 1)) == TRUE) {
102 $success = TRUE;
103 }
104 break;
105 case 'disable':
106 return FALSE;
107 break;
108 }
109
110 return $success;
111 }
112
113 /**
114 * Destructor:
115 * Releases lock automatically when instance is destroyed.
116 *
117 * @return void
118 */
119 function __destruct() {
120 $this->release();
121 }
122
123 /**
124 * Acquire a lock and return when successful. If the lock is already open, the client will be
125 *
126 * It is important to know that the lock will be acquired in any case, even if the request was blocked first. Therefore, the lock needs to be released in every situation.
127 *
128 * @return boolean Returns true if lock could be acquired without waiting, false otherwise.
129 */
130 public function acquire() {
131 $noWait = TRUE; // Default is TRUE, which means continue without caring for other clients. In the case of TYPO3s cache management, this has no negative effect except some resource overhead.
132 $isAcquired = TRUE;
133
134 switch ($this->method) {
135 case 'simple':
136 if (is_file($this->resource)) {
137 $this->sysLog('Waiting for a different process to release the lock');
138 $maxExecutionTime = ini_get('max_execution_time');
139 $maxAge = time() - ($maxExecutionTime ? $maxExecutionTime : 120);
140 if (@filectime($this->resource) < $maxAge) {
141 @unlink($this->resource);
142 $this->sysLog('Unlink stale lockfile');
143 }
144 }
145
146 $isAcquired = FALSE;
147 for ($i = 0; $i < $this->loops; $i++) {
148 $filepointer = @fopen($this->resource, 'x');
149 if ($filepointer !== FALSE) {
150 fclose($filepointer);
151 $this->sysLog('Lock aquired');
152 $noWait = ($i === 0);
153 $isAcquired = TRUE;
154 break;
155 }
156 usleep($this->step * 1000);
157 }
158
159 if (!$isAcquired) {
160 throw new Exception('Lock file could not be created');
161 }
162
163 t3lib_div::fixPermissions($this->resource);
164 break;
165 case 'flock':
166 if (($this->filepointer = fopen($this->resource, 'w+')) == FALSE) {
167 throw new Exception('Lock file could not be opened');
168 }
169
170 if (flock($this->filepointer, LOCK_EX | LOCK_NB) == TRUE) { // Lock without blocking
171 $noWait = TRUE;
172 } elseif (flock($this->filepointer, LOCK_EX) == TRUE) { // Lock with blocking (waiting for similar locks to become released)
173 $noWait = FALSE;
174 } else {
175 throw new Exception('Could not lock file "' . $this->resource . '"');
176 }
177 break;
178 case 'semaphore':
179 if (sem_acquire($this->resource)) {
180 // Unfortunately it seems not possible to find out if the request was blocked, so we return FALSE in any case to make sure the operation is tried again.
181 $noWait = FALSE;
182 }
183 break;
184 case 'disable':
185 $noWait = FALSE;
186 $isAcquired = FALSE;
187 break;
188 }
189
190 $this->isAcquired = $isAcquired;
191 return $noWait;
192 }
193
194 /**
195 * Release the lock
196 *
197 * @return boolean Returns TRUE on success or FALSE on failure
198 */
199 public function release() {
200 if (!$this->isAcquired) {
201 return TRUE;
202 }
203
204 $success = TRUE;
205 switch ($this->method) {
206 case 'simple':
207 if (unlink($this->resource) == FALSE) {
208 $success = FALSE;
209 }
210 break;
211 case 'flock':
212 if (flock($this->filepointer, LOCK_UN) == FALSE) {
213 $success = FALSE;
214 }
215 fclose($this->filepointer);
216 unlink($this->resource);
217 break;
218 case 'semaphore':
219 if (@sem_release($this->resource)) {
220 sem_remove($this->resource);
221 } else {
222 $success = FALSE;
223 }
224 break;
225 case 'disable':
226 $success = FALSE;
227 break;
228 }
229
230 $this->isAcquired = FALSE;
231 return $success;
232 }
233
234 /**
235 * Return the locking method which is currently used
236 *
237 * @return string Locking method
238 */
239 public function getMethod() {
240 return $this->method;
241 }
242
243 /**
244 * Return the ID which is currently used
245 *
246 * @return string Locking ID
247 */
248 public function getId() {
249 return $this->id;
250 }
251
252 /**
253 * Return the resource which is currently used.
254 * Depending on the locking method this can be a filename or a semaphore resource.
255 *
256 * @return mixed Locking resource (filename as string or semaphore as resource)
257 */
258 public function getResource() {
259 return $this->resource;
260 }
261
262 /**
263 * Return the status of a lock
264 *
265 * @return string Returns TRUE if lock is acquired, FALSE otherwise
266 */
267 public function getLockStatus() {
268 return $this->isAcquired;
269 }
270
271 /**
272 * Adds a common log entry for this locking API using t3lib_div::sysLog().
273 * Example: 25-02-08 17:58 - cms: Locking [simple::0aeafd2a67a6bb8b9543fb9ea25ecbe2]: Acquired
274 *
275 * @param string $message: The message to be logged
276 * @param integer $severity: Severity - 0 is info (default), 1 is notice, 2 is warning, 3 is error, 4 is fatal error
277 * @return void
278 */
279 public function sysLog($message, $severity = 0) {
280 t3lib_div::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), 'cms', $severity);
281 }
282 }
283
284
285 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_lock.php'])) {
286 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_lock.php']);
287 }
288 ?>