TYPO3 CMS  TYPO3_6-2
Locker.php
Go to the documentation of this file.
1 <?php
3 
17 
28 class Locker {
29 
30  const LOCKING_METHOD_SIMPLE = 'simple';
31  const LOCKING_METHOD_FLOCK = 'flock';
32  const LOCKING_METHOD_SEMAPHORE = 'semaphore';
33  const LOCKING_METHOD_DISABLED = 'disable';
34 
35  const FILE_LOCK_FOLDER = 'typo3temp/locks/';
36 
40  protected $method = '';
41 
45  protected $id;
46 
50  protected $resource;
51 
55  protected $filePointer;
56 
60  protected $isAcquired = FALSE;
61 
65  protected $loops = 150;
66 
70  protected $step = 200;
71 
75  protected $syslogFacility = 'cms';
76 
80  protected $isLoggingEnabled = TRUE;
81 
95  public function __construct($id, $method = self::LOCKING_METHOD_SIMPLE, $loops = 0, $step = 0) {
96  // Force ID to be string
97  $id = (string)$id;
98  if ((int)$loops) {
99  $this->loops = (int)$loops;
100  }
101  if ((int)$step) {
102  $this->step = (int)$step;
103  }
104  if ($method === '' && isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['lockingMode'])) {
105  $method = (string)$GLOBALS['TYPO3_CONF_VARS']['SYS']['lockingMode'];
106  }
107 
108  switch ($method) {
109  case self::LOCKING_METHOD_SIMPLE:
110  // intended fall through
111  case self::LOCKING_METHOD_FLOCK:
112  $this->id = md5($id);
113  $this->createPathIfNeeded();
114  break;
115  case self::LOCKING_METHOD_SEMAPHORE:
116  $this->id = abs(crc32($id));
117  break;
118  case self::LOCKING_METHOD_DISABLED:
119  break;
120  default:
121  throw new \InvalidArgumentException('No such locking method "' . $method . '"', 1294586097);
122  }
123  $this->method = $method;
124  }
125 
130  public function __destruct() {
131  $this->release();
132  switch ($this->method) {
133  case self::LOCKING_METHOD_FLOCK:
134  if (
135  GeneralUtility::isAllowedAbsPath($this->resource)
136  && GeneralUtility::isFirstPartOfStr($this->resource, PATH_site . self::FILE_LOCK_FOLDER)
137  ) {
138  @unlink($this->resource);
139  }
140  break;
141  case self::LOCKING_METHOD_SEMAPHORE:
142  @sem_remove($this->resource);
143  break;
144  default:
145  // do nothing
146  }
147  }
148 
155  protected function getSemaphore() {
156  $this->resource = sem_get($this->id, 1);
157  if ($this->resource === FALSE) {
158  throw new \RuntimeException('Unable to get semaphore with id ' . $this->id, 1313828196);
159  }
160  }
161 
172  public function acquire() {
173  // TODO refactor locking in TSFE to use the new API, then this call can be logged
174  // GeneralUtility::logDeprecatedFunction();
175 
176  // Default is TRUE, which means continue without caring for other clients.
177  // In the case of TYPO3s cache management, this has no negative effect except some resource overhead.
178  $noWait = FALSE;
179  $isAcquired = FALSE;
180  switch ($this->method) {
181  case self::LOCKING_METHOD_SIMPLE:
182  if (file_exists($this->resource)) {
183  $this->sysLog('Waiting for a different process to release the lock');
184  $maxExecutionTime = (int)ini_get('max_execution_time');
185  $maxAge = time() - ($maxExecutionTime ?: 120);
186  if (@filectime($this->resource) < $maxAge) {
187  @unlink($this->resource);
188  $this->sysLog('Unlinking stale lockfile', GeneralUtility::SYSLOG_SEVERITY_WARNING);
189  }
190  }
191  for ($i = 0; $i < $this->loops; $i++) {
192  $filePointer = @fopen($this->resource, 'x');
193  if ($filePointer !== FALSE) {
194  fclose($filePointer);
195  GeneralUtility::fixPermissions($this->resource);
196  $this->sysLog('Lock acquired');
197  $noWait = $i === 0;
198  $isAcquired = TRUE;
199  break;
200  }
201  usleep($this->step * 1000);
202  }
203  if (!$isAcquired) {
204  throw new \RuntimeException('Lock file could not be created', 1294586098);
205  }
206  break;
207  case self::LOCKING_METHOD_FLOCK:
208  $this->filePointer = fopen($this->resource, 'c');
209  if ($this->filePointer === FALSE) {
210  throw new \RuntimeException('Lock file could not be opened', 1294586099);
211  }
212  // Lock without blocking
213  if (flock($this->filePointer, LOCK_EX | LOCK_NB)) {
214  $noWait = TRUE;
215  } elseif (flock($this->filePointer, LOCK_EX)) {
216  // Lock with blocking (waiting for similar locks to become released)
217  $noWait = FALSE;
218  } else {
219  throw new \RuntimeException('Could not lock file "' . $this->resource . '"', 1294586100);
220  }
221  $isAcquired = TRUE;
222  break;
223  case self::LOCKING_METHOD_SEMAPHORE:
224  $this->getSemaphore();
225  while (!$isAcquired) {
226  if (@sem_acquire($this->resource)) {
227  // Unfortunately it is not possible to find out if the request has blocked,
228  // as sem_acquire will block until we get the resource.
229  // So we do not set $noWait here at all
230  $isAcquired = TRUE;
231  }
232  }
233  break;
234  case self::LOCKING_METHOD_DISABLED:
235  break;
236  default:
237  // will never be reached
238  }
239  $this->isAcquired = $isAcquired;
240  return $noWait;
241  }
242 
249  public function acquireExclusiveLock() {
250  if ($this->isAcquired) {
251  return TRUE;
252  }
253  $this->isAcquired = FALSE;
254  switch ($this->method) {
255  case self::LOCKING_METHOD_SIMPLE:
256  if (file_exists($this->resource)) {
257  $this->sysLog('Waiting for a different process to release the lock');
258  $maxExecutionTime = (int)ini_get('max_execution_time');
259  $maxAge = time() - ($maxExecutionTime ?: 120);
260  if (@filectime($this->resource) < $maxAge) {
261  @unlink($this->resource);
262  $this->sysLog('Unlinking stale lockfile', GeneralUtility::SYSLOG_SEVERITY_WARNING);
263  }
264  }
265  for ($i = 0; $i < $this->loops; $i++) {
266  $filePointer = @fopen($this->resource, 'x');
267  if ($filePointer !== FALSE) {
268  fclose($filePointer);
269  GeneralUtility::fixPermissions($this->resource);
270  $this->sysLog('Lock acquired');
271  $this->isAcquired = TRUE;
272  break;
273  }
274  usleep($this->step * 1000);
275  }
276  break;
277  case self::LOCKING_METHOD_FLOCK:
278  $this->filePointer = fopen($this->resource, 'c');
279  if ($this->filePointer === FALSE) {
280  throw new \RuntimeException('Lock file could not be opened', 1294586099);
281  }
282  if (flock($this->filePointer, LOCK_EX)) {
283  $this->isAcquired = TRUE;
284  }
285  break;
286  case self::LOCKING_METHOD_SEMAPHORE:
287  $this->getSemaphore();
288  if (@sem_acquire($this->resource)) {
289  $this->isAcquired = TRUE;
290  }
291  break;
292  case self::LOCKING_METHOD_DISABLED:
293  break;
294  default:
295  // will never be reached
296  }
297  return $this->isAcquired;
298  }
299 
308  public function acquireSharedLock() {
309  if ($this->isAcquired) {
310  return TRUE;
311  }
312  if ($this->method === self::LOCKING_METHOD_FLOCK) {
313  $this->filePointer = fopen($this->resource, 'c');
314  if ($this->filePointer === FALSE) {
315  throw new \RuntimeException('Lock file could not be opened', 1294586099);
316  }
317  if (flock($this->filePointer, LOCK_SH)) {
318  $this->isAcquired = TRUE;
319  }
320  }
321  return $this->isAcquired;
322  }
323 
329  public function release() {
330  if (!$this->isAcquired) {
331  return TRUE;
332  }
333  $success = TRUE;
334  switch ($this->method) {
335  case self::LOCKING_METHOD_SIMPLE:
336  if (
337  GeneralUtility::isAllowedAbsPath($this->resource)
338  && GeneralUtility::isFirstPartOfStr($this->resource, PATH_site . self::FILE_LOCK_FOLDER)
339  ) {
340  if (@unlink($this->resource) === FALSE) {
341  $success = FALSE;
342  }
343  }
344  break;
345  case self::LOCKING_METHOD_FLOCK:
346  if (is_resource($this->filePointer)) {
347  if (flock($this->filePointer, LOCK_UN) === FALSE) {
348  $success = FALSE;
349  }
350  fclose($this->filePointer);
351  }
352  break;
353  case self::LOCKING_METHOD_SEMAPHORE:
354  if (!@sem_release($this->resource)) {
355  $success = FALSE;
356  }
357  break;
358  case self::LOCKING_METHOD_DISABLED:
359  break;
360  default:
361  // will never be reached
362  }
363  $this->isAcquired = FALSE;
364  return $success;
365  }
366 
372  public function getMethod() {
373  return $this->method;
374  }
375 
381  public function getId() {
382  return $this->id;
383  }
384 
391  public function getResource() {
392  return $this->resource;
393  }
394 
400  public function getLockStatus() {
401  return $this->isAcquired;
402  }
403 
409  public function isLocked() {
410  $result = FALSE;
411  switch ($this->method) {
412  case self::LOCKING_METHOD_SIMPLE:
413  if (file_exists($this->resource)) {
414  $maxExecutionTime = (int)ini_get('max_execution_time');
415  $maxAge = time() - ($maxExecutionTime ?: 120);
416  if (@filectime($this->resource) < $maxAge) {
417  @unlink($this->resource);
418  $this->sysLog('Unlinking stale lockfile', GeneralUtility::SYSLOG_SEVERITY_WARNING);
419  } else {
420  $result = TRUE;
421  }
422  }
423  break;
424  case self::LOCKING_METHOD_FLOCK:
425  // we can't detect this reliably here, since the third parameter of flock() does not work on windows
426  break;
427  case self::LOCKING_METHOD_SEMAPHORE:
428  // no way to detect this at all, no PHP API for that
429  break;
430  case self::LOCKING_METHOD_DISABLED:
431  break;
432  default:
433  // will never be reached
434  }
435  return $result;
436  }
437 
444  $this->syslogFacility = $syslogFacility;
445  }
446 
453  $this->isLoggingEnabled = $isLoggingEnabled;
454  }
455 
464  public function sysLog($message, $severity = 0) {
465  if ($this->isLoggingEnabled) {
466  GeneralUtility::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), $this->syslogFacility, $severity);
467  }
468  }
469 
478  protected function createPathIfNeeded() {
479  $path = PATH_site . self::FILE_LOCK_FOLDER;
480  if (!is_dir($path)) {
481  // Not using mkdir_deep on purpose here, if typo3temp itself
482  // does not exist, this issue should be solved on a different
483  // level of the application.
484  if (!GeneralUtility::mkdir($path)) {
485  throw new \RuntimeException('Cannot create directory ' . $path, 1395140007);
486  }
487  }
488  if (!is_writable($path)) {
489  throw new \RuntimeException('Cannot write to directory ' . $path, 1396278700);
490  }
491  $this->resource = $path . $this->id;
492  }
493 }
static isFirstPartOfStr($str, $partStr)
sysLog($message, $severity=0)
Definition: Locker.php:464
static fixPermissions($path, $recursive=FALSE)
setEnableLogging($isLoggingEnabled)
Definition: Locker.php:452
__construct($id, $method=self::LOCKING_METHOD_SIMPLE, $loops=0, $step=0)
Definition: Locker.php:95
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
setSyslogFacility($syslogFacility)
Definition: Locker.php:443