TYPO3 CMS  TYPO3_7-6
Locker.php
Go to the documentation of this file.
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 
18 
26 class Locker
27 {
28  const LOCKING_METHOD_SIMPLE = 'simple';
29  const LOCKING_METHOD_FLOCK = 'flock';
30  const LOCKING_METHOD_SEMAPHORE = 'semaphore';
31  const LOCKING_METHOD_DISABLED = 'disable';
32 
33  const FILE_LOCK_FOLDER = 'typo3temp/locks/';
34 
38  protected $method = '';
39 
43  protected $id;
44 
48  protected $resource;
49 
53  protected $filePointer;
54 
58  protected $isAcquired = false;
59 
63  protected $loops = 150;
64 
68  protected $step = 200;
69 
73  protected $syslogFacility = 'cms';
74 
78  protected $isLoggingEnabled = true;
79 
94  public function __construct($id, $method = self::LOCKING_METHOD_SIMPLE, $loops = 0, $step = 0)
95  {
97  // Force ID to be string
98  $id = (string)$id;
99  if ((int)$loops) {
100  $this->loops = (int)$loops;
101  }
102  if ((int)$step) {
103  $this->step = (int)$step;
104  }
105  if ($method === '' && isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['lockingMode'])) {
106  $method = (string)$GLOBALS['TYPO3_CONF_VARS']['SYS']['lockingMode'];
107  }
108 
109  switch ($method) {
110  case self::LOCKING_METHOD_SIMPLE:
111  // intended fall through
112  case self::LOCKING_METHOD_FLOCK:
113  $this->id = md5($id);
114  $this->createPathIfNeeded();
115  break;
116  case self::LOCKING_METHOD_SEMAPHORE:
117  $this->id = abs(crc32($id));
118  break;
119  case self::LOCKING_METHOD_DISABLED:
120  break;
121  default:
122  throw new \InvalidArgumentException('No such locking method "' . $method . '"', 1294586097);
123  }
124  $this->method = $method;
125  }
126 
131  public function __destruct()
132  {
133  $this->release();
134  switch ($this->method) {
135  case self::LOCKING_METHOD_FLOCK:
136  if (
137  GeneralUtility::isAllowedAbsPath($this->resource)
138  && GeneralUtility::isFirstPartOfStr($this->resource, PATH_site . self::FILE_LOCK_FOLDER)
139  ) {
140  @unlink($this->resource);
141  }
142  break;
143  case self::LOCKING_METHOD_SEMAPHORE:
144  @sem_remove($this->resource);
145  break;
146  default:
147  // do nothing
148  }
149  }
150 
157  protected function getSemaphore()
158  {
159  $this->resource = sem_get($this->id, 1);
160  if ($this->resource === false) {
161  throw new \RuntimeException('Unable to get semaphore with id ' . $this->id, 1313828196);
162  }
163  }
164 
175  public function acquire()
176  {
177  // @todo refactor locking in TSFE to use the new API, then this call can be logged
178  // GeneralUtility::logDeprecatedFunction();
179 
180  // Default is TRUE, which means continue without caring for other clients.
181  // In the case of TYPO3s cache management, this has no negative effect except some resource overhead.
182  $noWait = false;
183  $isAcquired = false;
184  switch ($this->method) {
185  case self::LOCKING_METHOD_SIMPLE:
186  if (file_exists($this->resource)) {
187  $this->sysLog('Waiting for a different process to release the lock');
188  $maxExecutionTime = (int)ini_get('max_execution_time');
189  $maxAge = time() - ($maxExecutionTime ?: 120);
190  if (@filectime($this->resource) < $maxAge) {
191  @unlink($this->resource);
192  $this->sysLog('Unlinking stale lockfile', GeneralUtility::SYSLOG_SEVERITY_WARNING);
193  }
194  }
195  for ($i = 0; $i < $this->loops; $i++) {
196  $filePointer = @fopen($this->resource, 'x');
197  if ($filePointer !== false) {
198  fclose($filePointer);
199  GeneralUtility::fixPermissions($this->resource);
200  $this->sysLog('Lock acquired');
201  $noWait = $i === 0;
202  $isAcquired = true;
203  break;
204  }
205  usleep($this->step * 1000);
206  }
207  if (!$isAcquired) {
208  throw new \RuntimeException('Lock file could not be created', 1294586098);
209  }
210  break;
211  case self::LOCKING_METHOD_FLOCK:
212  $this->filePointer = fopen($this->resource, 'c');
213  if ($this->filePointer === false) {
214  throw new \RuntimeException('Lock file could not be opened', 1294586099);
215  }
216  // Lock without blocking
217  if (flock($this->filePointer, LOCK_EX | LOCK_NB)) {
218  $noWait = true;
219  } elseif (flock($this->filePointer, LOCK_EX)) {
220  // Lock with blocking (waiting for similar locks to become released)
221  $noWait = false;
222  } else {
223  throw new \RuntimeException('Could not lock file "' . $this->resource . '"', 1294586100);
224  }
225  $isAcquired = true;
226  break;
227  case self::LOCKING_METHOD_SEMAPHORE:
228  $this->getSemaphore();
229  while (!$isAcquired) {
230  if (@sem_acquire($this->resource)) {
231  // Unfortunately it is not possible to find out if the request has blocked,
232  // as sem_acquire will block until we get the resource.
233  // So we do not set $noWait here at all
234  $isAcquired = true;
235  }
236  }
237  break;
238  case self::LOCKING_METHOD_DISABLED:
239  break;
240  default:
241  // will never be reached
242  }
243  $this->isAcquired = $isAcquired;
244  return $noWait;
245  }
246 
253  public function acquireExclusiveLock()
254  {
255  if ($this->isAcquired) {
256  return true;
257  }
258  $this->isAcquired = false;
259  switch ($this->method) {
260  case self::LOCKING_METHOD_SIMPLE:
261  if (file_exists($this->resource)) {
262  $this->sysLog('Waiting for a different process to release the lock');
263  $maxExecutionTime = (int)ini_get('max_execution_time');
264  $maxAge = time() - ($maxExecutionTime ?: 120);
265  if (@filectime($this->resource) < $maxAge) {
266  @unlink($this->resource);
267  $this->sysLog('Unlinking stale lockfile', GeneralUtility::SYSLOG_SEVERITY_WARNING);
268  }
269  }
270  for ($i = 0; $i < $this->loops; $i++) {
271  $filePointer = @fopen($this->resource, 'x');
272  if ($filePointer !== false) {
273  fclose($filePointer);
274  GeneralUtility::fixPermissions($this->resource);
275  $this->sysLog('Lock acquired');
276  $this->isAcquired = true;
277  break;
278  }
279  usleep($this->step * 1000);
280  }
281  break;
282  case self::LOCKING_METHOD_FLOCK:
283  $this->filePointer = fopen($this->resource, 'c');
284  if ($this->filePointer === false) {
285  throw new \RuntimeException('Lock file could not be opened', 1294586099);
286  }
287  if (flock($this->filePointer, LOCK_EX)) {
288  $this->isAcquired = true;
289  }
290  break;
291  case self::LOCKING_METHOD_SEMAPHORE:
292  $this->getSemaphore();
293  if (@sem_acquire($this->resource)) {
294  $this->isAcquired = true;
295  }
296  break;
297  case self::LOCKING_METHOD_DISABLED:
298  break;
299  default:
300  // will never be reached
301  }
302  return $this->isAcquired;
303  }
304 
313  public function acquireSharedLock()
314  {
315  if ($this->isAcquired) {
316  return true;
317  }
318  if ($this->method === self::LOCKING_METHOD_FLOCK) {
319  $this->filePointer = fopen($this->resource, 'c');
320  if ($this->filePointer === false) {
321  throw new \RuntimeException('Lock file could not be opened', 1294586099);
322  }
323  if (flock($this->filePointer, LOCK_SH)) {
324  $this->isAcquired = true;
325  }
326  }
327  return $this->isAcquired;
328  }
329 
335  public function release()
336  {
337  if (!$this->isAcquired) {
338  return true;
339  }
340  $success = true;
341  switch ($this->method) {
342  case self::LOCKING_METHOD_SIMPLE:
343  if (
344  GeneralUtility::isAllowedAbsPath($this->resource)
345  && GeneralUtility::isFirstPartOfStr($this->resource, PATH_site . self::FILE_LOCK_FOLDER)
346  ) {
347  if (@unlink($this->resource) === false) {
348  $success = false;
349  }
350  }
351  break;
352  case self::LOCKING_METHOD_FLOCK:
353  if (is_resource($this->filePointer)) {
354  if (flock($this->filePointer, LOCK_UN) === false) {
355  $success = false;
356  }
357  fclose($this->filePointer);
358  }
359  break;
360  case self::LOCKING_METHOD_SEMAPHORE:
361  if (!@sem_release($this->resource)) {
362  $success = false;
363  }
364  break;
365  case self::LOCKING_METHOD_DISABLED:
366  break;
367  default:
368  // will never be reached
369  }
370  $this->isAcquired = false;
371  return $success;
372  }
373 
379  public function getMethod()
380  {
381  return $this->method;
382  }
383 
389  public function getId()
390  {
391  return $this->id;
392  }
393 
400  public function getResource()
401  {
402  return $this->resource;
403  }
404 
410  public function getLockStatus()
411  {
412  return $this->isAcquired;
413  }
414 
420  public function isLocked()
421  {
422  $result = false;
423  switch ($this->method) {
424  case self::LOCKING_METHOD_SIMPLE:
425  if (file_exists($this->resource)) {
426  $maxExecutionTime = (int)ini_get('max_execution_time');
427  $maxAge = time() - ($maxExecutionTime ?: 120);
428  if (@filectime($this->resource) < $maxAge) {
429  @unlink($this->resource);
430  $this->sysLog('Unlinking stale lockfile', GeneralUtility::SYSLOG_SEVERITY_WARNING);
431  } else {
432  $result = true;
433  }
434  }
435  break;
436  case self::LOCKING_METHOD_FLOCK:
437  // we can't detect this reliably here, since the third parameter of flock() does not work on windows
438  break;
439  case self::LOCKING_METHOD_SEMAPHORE:
440  // no way to detect this at all, no PHP API for that
441  break;
442  case self::LOCKING_METHOD_DISABLED:
443  break;
444  default:
445  // will never be reached
446  }
447  return $result;
448  }
449 
456  {
457  $this->syslogFacility = $syslogFacility;
458  }
459 
466  {
467  $this->isLoggingEnabled = $isLoggingEnabled;
468  }
469 
478  public function sysLog($message, $severity = 0)
479  {
480  if ($this->isLoggingEnabled) {
481  GeneralUtility::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), $this->syslogFacility, $severity);
482  }
483  }
484 
493  protected function createPathIfNeeded()
494  {
495  $path = PATH_site . self::FILE_LOCK_FOLDER;
496  if (!is_dir($path)) {
497  // Not using mkdir_deep on purpose here, if typo3temp itself
498  // does not exist, this issue should be solved on a different
499  // level of the application.
500  if (!GeneralUtility::mkdir($path)) {
501  throw new \RuntimeException('Cannot create directory ' . $path, 1395140007);
502  }
503  }
504  if (!is_writable($path)) {
505  throw new \RuntimeException('Cannot write to directory ' . $path, 1396278700);
506  }
507  $this->resource = $path . $this->id;
508  }
509 }
static isFirstPartOfStr($str, $partStr)
sysLog($message, $severity=0)
Definition: Locker.php:478
setEnableLogging($isLoggingEnabled)
Definition: Locker.php:465
__construct($id, $method=self::LOCKING_METHOD_SIMPLE, $loops=0, $step=0)
Definition: Locker.php:94
static fixPermissions($path, $recursive=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
setSyslogFacility($syslogFacility)
Definition: Locker.php:455