TYPO3 API  SVNRelease
class.t3lib_lock.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2008-2011 Michael Stucki (michael@typo3.org)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 /**
00028  * Class for providing locking features in TYPO3
00029  *
00030  * $Id: class.t3lib_lock.php 10297 2011-01-25 10:17:36Z baschny $
00031  *
00032  * @author  Michael Stucki <michael@typo3.org>
00033  */
00034 
00035 
00036 /**
00037  * TYPO3 locking class
00038  * This class provides an abstract layer to various locking features for TYPO3
00039  *
00040  * It is intended to blocks requests until some data has been generated.
00041  * 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.
00042  *
00043  * @author  Michael Stucki <michael@typo3.org>
00044  * @package TYPO3
00045  * @subpackage t3lib
00046  * @see class.t3lib_tstemplate.php, class.tslib_fe.php
00047  */
00048 class t3lib_lock {
00049     protected $method;
00050     protected $id; // Identifier used for this lock
00051     protected $resource; // Resource used for this lock (can be a file or a semaphore resource)
00052     protected $filepointer;
00053     protected $isAcquired = FALSE;
00054 
00055     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.
00056     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.
00057     protected $syslogFacility = 'cms';
00058     protected $isLoggingEnabled = TRUE;
00059 
00060 
00061     /**
00062      * Constructor:
00063      * initializes locking, check input parameters and set variables accordingly.
00064      *
00065      * @param   string      ID to identify this lock in the system
00066      * @param   string      Define which locking method to use. Defaults to "simple".
00067      * @param   integer     Number of times a locked resource is tried to be acquired. This is only used by manual locks like the "simple" method.
00068      * @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.
00069      * @return  boolean     Returns true unless something went wrong
00070      */
00071     public function __construct($id, $method = '', $loops = 0, $step = 0) {
00072 
00073             // Input checks
00074         $id = (string) $id; // Force ID to be string
00075         if (intval($loops)) {
00076             $this->loops = intval($loops);
00077         }
00078         if (intval($step)) {
00079             $this->step = intval($step);
00080         }
00081 
00082             // Detect locking method
00083         if (in_array($method, array('disable', 'simple', 'flock', 'semaphore'))) {
00084             $this->method = $method;
00085         } else {
00086             throw new Exception('No such method "' . $method . '"');
00087         }
00088 
00089         $success = FALSE;
00090         switch ($this->method) {
00091             case 'simple':
00092             case 'flock':
00093                 $path = PATH_site . 'typo3temp/locks/';
00094                 if (!is_dir($path)) {
00095                     t3lib_div::mkdir($path);
00096                 }
00097                 $this->id = md5($id);
00098                 $this->resource = $path . $this->id;
00099                 $success = TRUE;
00100             break;
00101             case 'semaphore':
00102                 $this->id = abs(crc32($id));
00103                 if (($this->resource = sem_get($this->id, 1)) == TRUE) {
00104                     $success = TRUE;
00105                 }
00106             break;
00107             case 'disable':
00108                 return FALSE;
00109             break;
00110         }
00111 
00112         return $success;
00113     }
00114 
00115     /**
00116      * Destructor:
00117      * Releases lock automatically when instance is destroyed.
00118      *
00119      * @return  void
00120      */
00121     function __destruct() {
00122         $this->release();
00123     }
00124 
00125     /**
00126      * Acquire a lock and return when successful. If the lock is already open, the client will be
00127      *
00128      * 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.
00129      *
00130      * @return  boolean     Returns true if lock could be acquired without waiting, false otherwise.
00131      */
00132     public function acquire() {
00133         $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.
00134         $isAcquired = TRUE;
00135 
00136         switch ($this->method) {
00137             case 'simple':
00138                 if (is_file($this->resource)) {
00139                     $this->sysLog('Waiting for a different process to release the lock');
00140                     $maxExecutionTime = ini_get('max_execution_time');
00141                     $maxAge = time() - ($maxExecutionTime ? $maxExecutionTime : 120);
00142                     if (@filectime($this->resource) < $maxAge) {
00143                         @unlink($this->resource);
00144                         $this->sysLog('Unlink stale lockfile');
00145                     }
00146                 }
00147 
00148                 $isAcquired = FALSE;
00149                 for ($i = 0; $i < $this->loops; $i++) {
00150                     $filepointer = @fopen($this->resource, 'x');
00151                     if ($filepointer !== FALSE) {
00152                         fclose($filepointer);
00153                         $this->sysLog('Lock aquired');
00154                         $noWait = ($i === 0);
00155                         $isAcquired = TRUE;
00156                         break;
00157                     }
00158                     usleep($this->step * 1000);
00159                 }
00160 
00161                 if (!$isAcquired) {
00162                     throw new Exception('Lock file could not be created');
00163                 }
00164 
00165                 t3lib_div::fixPermissions($this->resource);
00166             break;
00167             case 'flock':
00168                 if (($this->filepointer = fopen($this->resource, 'w+')) == FALSE) {
00169                     throw new Exception('Lock file could not be opened');
00170                 }
00171 
00172                 if (flock($this->filepointer, LOCK_EX | LOCK_NB) == TRUE) { // Lock without blocking
00173                     $noWait = TRUE;
00174                 } elseif (flock($this->filepointer, LOCK_EX) == TRUE) { // Lock with blocking (waiting for similar locks to become released)
00175                     $noWait = FALSE;
00176                 } else {
00177                     throw new Exception('Could not lock file "' . $this->resource . '"');
00178                 }
00179             break;
00180             case 'semaphore':
00181                 if (sem_acquire($this->resource)) {
00182                         // 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.
00183                     $noWait = FALSE;
00184                 }
00185             break;
00186             case 'disable':
00187                 $noWait = FALSE;
00188                 $isAcquired = FALSE;
00189             break;
00190         }
00191 
00192         $this->isAcquired = $isAcquired;
00193         return $noWait;
00194     }
00195 
00196     /**
00197      * Release the lock
00198      *
00199      * @return  boolean     Returns TRUE on success or FALSE on failure
00200      */
00201     public function release() {
00202         if (!$this->isAcquired) {
00203             return TRUE;
00204         }
00205 
00206         $success = TRUE;
00207         switch ($this->method) {
00208             case 'simple':
00209                 if (unlink($this->resource) == FALSE) {
00210                     $success = FALSE;
00211                 }
00212             break;
00213             case 'flock':
00214                 if (flock($this->filepointer, LOCK_UN) == FALSE) {
00215                     $success = FALSE;
00216                 }
00217                 fclose($this->filepointer);
00218                 unlink($this->resource);
00219             break;
00220             case 'semaphore':
00221                 if (@sem_release($this->resource)) {
00222                     sem_remove($this->resource);
00223                 } else {
00224                     $success = FALSE;
00225                 }
00226             break;
00227             case 'disable':
00228                 $success = FALSE;
00229             break;
00230         }
00231 
00232         $this->isAcquired = FALSE;
00233         return $success;
00234     }
00235 
00236     /**
00237      * Return the locking method which is currently used
00238      *
00239      * @return  string      Locking method
00240      */
00241     public function getMethod() {
00242         return $this->method;
00243     }
00244 
00245     /**
00246      * Return the ID which is currently used
00247      *
00248      * @return  string      Locking ID
00249      */
00250     public function getId() {
00251         return $this->id;
00252     }
00253 
00254     /**
00255      * Return the resource which is currently used.
00256      * Depending on the locking method this can be a filename or a semaphore resource.
00257      *
00258      * @return  mixed       Locking resource (filename as string or semaphore as resource)
00259      */
00260     public function getResource() {
00261         return $this->resource;
00262     }
00263 
00264     /**
00265      * Return the status of a lock
00266      *
00267      * @return  string      Returns TRUE if lock is acquired, FALSE otherwise
00268      */
00269     public function getLockStatus() {
00270         return $this->isAcquired;
00271     }
00272 
00273     /**
00274      * Sets the facility (extension name) for the syslog entry.
00275      *
00276      * @param string $syslogFacility
00277      */
00278     public function setSyslogFacility($syslogFacility) {
00279         $this->syslogFacility = $syslogFacility;
00280     }
00281 
00282     /**
00283      * Enable/ disable logging
00284      *
00285      * @param boolean $isLoggingEnabled
00286      */
00287     public function setEnableLogging($isLoggingEnabled) {
00288         $this->isLoggingEnabled = $isLoggingEnabled;
00289     }
00290 
00291     /**
00292      * Adds a common log entry for this locking API using t3lib_div::sysLog().
00293      * Example: 25-02-08 17:58 - cms: Locking [simple::0aeafd2a67a6bb8b9543fb9ea25ecbe2]: Acquired
00294      *
00295      * @param   string      $message: The message to be logged
00296      * @param   integer     $severity: Severity - 0 is info (default), 1 is notice, 2 is warning, 3 is error, 4 is fatal error
00297      * @return  void
00298      */
00299     public function sysLog($message, $severity = 0) {
00300         if ($this->isLoggingEnabled) {
00301             t3lib_div::sysLog('Locking [' . $this->method . '::' . $this->id . ']: ' . trim($message), $this->syslogFacility, $severity);
00302         }
00303     }
00304 }
00305 
00306 
00307 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_lock.php'])) {
00308     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_lock.php']);
00309 }
00310 ?>