Project

General

Profile

Bug #18084 » lockPageGeneration_v3.diff

Administrator Admin, 2008-02-08 00:00

View differences:

t3lib/config_default.php (Arbeitskopie)
'systemLogLevel' => 0, // Integer: Only messages with same or higher severity are logged; 0 is info, 1 is notice, 2 is warning, 3 is error, 4 is fatal error.
'maxFileNameLength' => 60, // Integer, This is the maximum file name length. The value will be taken into account by basic file operations like renaming or creation of files and folders.
'UTF8filesystem' => 0, // Boolean: If true and [BE][forceCharset] is set to utf-8, then TYPO3 uses utf-8 to store file names. This allows for accented Latin letters as well as any other non-latin characters like Cyrillic and Chinese.
'lockingMode' => 'simple', // String: Define which locking mode is used to control requests to pages being generated. Can be one of either "disable" (no locking), "simple" (checks for file existance), "flock" (using PHPs flock() function), "semaphore" (using PHPs sem_acquire() function). Default is "disable".
),
'EXT' => Array ( // Options related to the Extension Management
'noEdit' => 1, // Boolean: If set, the Extension Manager does NOT allow extension files to be edited! (Otherwise both local and global extensions can be edited.)
t3lib/class.t3lib_lock.php (Revision 0)
<?php
/***************************************************************
* Copyright notice
*
* (c) 2008 Michael Stucki (michael@typo3.org)
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
* Class for providing locking features in TYPO3
*
* $Id$
*
* @author Michael Stucki <michael@typo3.org>
*/
require_once(PATH_t3lib.'class.t3lib_div.php');
/**
* TYPO3 locking class
* This class provides an abstract layer to various locking features for TYPO3
*
* It is intended to blocks requests until some data has been generated.
* 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.
*
* @author Michael Stucki <michael@typo3.org>
* @package TYPO3
* @subpackage t3lib
* @see class.t3lib_tstemplate.php, class.tslib_fe.php
*/
class t3lib_lock {
private $method;
private $id; // Identifier used for this lock
private $filepointer;
private $isAcquired = false;
private $loops = 150; // Number of times a locked resource is tried to be acquired. This is only used by manual locks like the "simple" method.
private $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.
/**
* Constructor:
* initializes locking, check input parameters and set variables accordingly.
*
* @param string ID to identify this lock in the system
* @param string Define which locking method to use. Defaults to "simple".
* @param integer Number of times a locked resource is tried to be acquired. This is only used by manual locks like the "simple" method.
* @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.
* @return boolean Returns true unless something went wrong
*/
public function __construct($id, $method='', $loops=0, $steps=0) {
// Input checks
$id = (string)$id; // Force ID to be string
if (intval($loops)) {
$this->loops = intval($loops);
}
if (intval($step)) {
$this->step = intval($step);
}
// Detect locking method
if (in_array($method, array('disable', 'simple','flock','semaphore'))) {
$this->method = $method;
} else {
throw new Exception('No such method "'.$method.'"');
}
$success = false;
switch ($this->method) {
case 'simple':
case 'flock':
$path = PATH_site.'typo3temp/locks/';
$this->id = $path.md5($id);
$success = true;
break;
case 'semaphore':
$id = abs(crc32($id));
if (($this->id = sem_get($id, 1))==true) {
$success = true;
}
break;
case 'disable':
return false;
break;
}
return $success;
}
/**
* Acquire a lock and return when successful. If the lock is already open, the client will be
*
* 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.
*
* @return boolean Returns true if lock could be acquired without waiting, false otherwise.
*/
public function acquire() {
$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.
$isAcquired = true;
switch ($this->method) {
case 'simple':
if (is_file($this->id)) {
$i = 0;
while ($i<$this->loops) {
$i++;
usleep($this->step*1000);
clearstatcache();
if (!is_file($this->id)) { // Lock became free, leave the loop
$noWait = false;
break;
}
}
} else {
$noWait = true;
}
if (($this->filepointer = touch($this->id)) == false) {
throw new Exception('Lock file could not be created');
}
break;
case 'flock':
if (($this->filepointer = fopen($this->id, 'w+')) == false) {
throw new Exception('Lock file could not be opened');
}
if (flock($this->filepointer, LOCK_EX|LOCK_NB) == true) { // Lock without blocking
$noWait = true;
} elseif (flock($this->filepointer, LOCK_EX) == true) { // Lock with blocking (waiting for similar locks to become released)
$noWait = false;
} else {
throw new Exception('Could not lock file "'.$this->id.'"');
}
break;
case 'semaphore':
if (sem_acquire($this->id)) {
// 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.
$noWait = false;
}
break;
case 'disable':
$noWait = false;
$isAcquired = false;
break;
}
$this->isAcquired = $isAcquired;
return $noWait;
}
/**
* Release the lock
*
* @return boolean Returns true on success or false on failure
*/
public function release() {
if (!$this->isAcquired) {
return true;
}
$success = true;
switch ($this->method) {
case 'simple':
if (unlink($this->id) == false) {
$success = false;
}
break;
case 'flock':
if (flock($this->filepointer, LOCK_UN) == false) {
$success = false;
}
fclose($this->filepointer);
unlink($this->id);
break;
case 'semaphore':
if (!sem_release($this->id)) {
$success = false;
}
break;
case 'disable':
$success = false;
break;
}
$this->isAcquired = false;
return $success;
}
/**
* Return the locking method which is currently used
*
* @return string Locking method
*/
public function getMethod() {
return $this->method;
}
/**
* Return the ID of which is currently used
*
* @return string Locking ID
*/
public function getId() {
return $this->id;
}
/**
* Return the status of a lock
*
* @return string Returns true if lock is acquired, false otherwise
*/
public function getLockStatus() {
return $this->isAcquired;
}
}
if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_lock.php']) {
include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_lock.php']);
}
?>
typo3/sysext/cms/tslib/class.tslib_fe.php (Arbeitskopie)
*
*/
require_once (PATH_t3lib.'class.t3lib_lock.php');
......
var $LL_labels_cache=array();
var $LL_files_cache=array();
/**
* Locking object
*
* @var t3lib_lock
*/
var $pagesection_lockObj; // Locking object for accessing "cache_pagesection"
/**
* Locking object
*
* @var t3lib_lock
*/
var $pages_lockObj; // Locking object for accessing "cache_pages"
/**
* Class constructor
* Takes a number of GET/POST input variable as arguments and stores them internally.
......
function getFromCache() {
if (!$this->no_cache) {
$cc = $this->tmpl->getCurrentPageData();
if (!is_array($cc)) {
$key = $this->id.'::'.$this->MP;
$isLocked = $this->acquirePageGenerationLock($this->pagesection_lockObj, $key); // Returns true if the lock is active now
if (!$isLocked) { // Lock is no longer active, the data in "cache_pagesection" is now ready
$cc = $this->tmpl->getCurrentPageData();
if (is_array($cc)) {
$this->releasePageGenerationLock($this->pagesection_lockObj); // Release the lock
}
}
}
if (is_array($cc)) {
// BE CAREFUL to change the content of the cc-array. This array is serialized and an md5-hash based on this is used for caching the page.
// If this hash is not the same in here in this section and after page-generation, then the page will not be properly cached!
......
$this->cacheContentFlag = 0;
// Look for page in cache only if caching is not disabled and if a shift-reload is not sent to the server.
if ($this->all && !$this->no_cache && !$this->headerNoCache()) {
if (!$this->no_cache && !$this->headerNoCache()) {
$lockHash = $this->getLockHash();
$this->newHash = $this->getHash();
if ($this->all) {
$this->newHash = $this->getHash();
$GLOBALS['TT']->push('Cache Row','');
$row = $this->getFromCache_queryRow();
if ($row) {
$GLOBALS['TT']->push('Cache Row','');
$row = $this->getFromCache_queryRow();
$this->config = (array)unserialize($row['cache_data']); // Fetches the lowlevel config stored with the cached data
$this->content = $row['HTML']; // Getting the content
$this->tempContent = $row['temp_content']; // Flag for temp content
$this->cacheContentFlag = 1; // Setting flag, so we know, that some cached content has been loaded
$this->cacheExpires = $row['expires'];
if (!is_array($row)) {
$isLocked = $this->acquirePageGenerationLock($this->pages_lockObj, $lockHash);
if (!$isLocked) { // Lock is no longer active, the data in "cache_pages" is now ready
$row = $this->getFromCache_queryRow();
if (is_array($row)) {
$this->releasePageGenerationLock($this->pages_lockObj); // Release the lock
}
}
}
if ($this->TYPO3_CONF_VARS['FE']['debug'] || $this->config['config']['debug']) {
$dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
$timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
if (is_array($row)) {
// Release this lock
$this->releasePageGenerationLock($this->pages_lockObj);
$this->content.= chr(10).'<!-- Cached page generated '.date($dateFormat.' '.$timeFormat, $row['tstamp']).'. Expires '.Date($dateFormat.' '.$timeFormat, $row['expires']).' -->';
$this->config = (array)unserialize($row['cache_data']); // Fetches the lowlevel config stored with the cached data
$this->content = $row['HTML']; // Getting the content
$this->tempContent = $row['temp_content']; // Flag for temp content
$this->cacheContentFlag = 1; // Setting flag, so we know, that some cached content has been loaded
$this->cacheExpires = $row['expires'];
if ($this->TYPO3_CONF_VARS['FE']['debug'] || $this->config['config']['debug']) {
$dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
$timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
$this->content.= chr(10).'<!-- Cached page generated '.date($dateFormat.' '.$timeFormat, $row['tstamp']).'. Expires '.Date($dateFormat.' '.$timeFormat, $row['expires']).' -->';
}
}
}
$GLOBALS['TT']->pull();
$GLOBALS['TT']->pull();
} else {
$this->acquirePageGenerationLock($this->pages_lockObj, $lockHash);
}
}
}
......
*
* @return string MD5 hash of $this->hash_base which is a serialized version of there variables.
* @access private
* @see getFromCache()
* @see getFromCache(), getLockHash()
*/
function getHash() {
$this->hash_base = serialize(
......
}
/**
* Calculates the lock-hash
* This hash is unique to the above hash, except that it doesn't contain the template information in $this->all.
*
* @return string MD5 hash
* @access private
* @see getFromCache(), getHash()
*/
function getLockHash() {
$lockHash = serialize(
array(
'id' => intval($this->id),
'type' => intval($this->type),
'gr_list' => (string)$this->gr_list,
'MP' => (string)$this->MP,
'cHash' => $this->cHash_array
)
);
return md5($lockHash);
}
/**
* Checks if config-array exists already but if not, gets it
*
* @return void
......
}
}
/**
* Lock the page generation process
* The lock is used to queue page requests until this page is successfully stored in the cache.
*
* @param object Reference to a locking object
* @param string String to identify the lock in the system
* @return boolean Returns true if the lock could be obtained, false otherwise (= process had to wait for existing lock to be released)
* @see releasePageGenerationLock()
*/
function acquirePageGenerationLock(&$lockObj, $key) {
if ($this->no_cache || $this->headerNoCache()) {
return true; // No locking is needed if caching is disabled
}
try {
if (!is_object($lockObj)) {
$className = t3lib_div::makeInstanceClassName('t3lib_lock');
$lockObj = new $className($key, $this->TYPO3_CONF_VARS['SYS']['lockingMode']);
}
$success = false;
if (strlen($key)) {
// true = Page could get locked without blocking
// false = Page could get locked but process was blocked before
$success = $lockObj->acquire();
}
} catch (Exception $e) {
t3lib_div::sysLog('Locking failed: '.$e->getMessage(), 'cms', 3);
$success = false; // If locking fails, return with false and continue without locking
}
return $success;
}
/**
* Release the page generation lock
*
* @param object Reference to a locking object
* @return boolean Returns true on success, false otherwise
* @see acquirePageGenerationLock()
*/
function releasePageGenerationLock(&$lockObj) {
if ($this->no_cache || $this->headerNoCache()) {
return true; // No locking is needed if caching is disabled
}
$success = false;
if (is_object($lockObj)) {
$success = $lockObj->release();
unset($lockObj);
}
return $success;
}
......
/********************************************
*
* Page generation; rendering and inclusion
......
$this->newHash = $this->getHash(); // Same codeline as in getFromCache(). But $this->all has been changed by t3lib_TStemplate::start() in the meantime, so this must be called again!
$this->config['hash_base'] = $this->hash_base; // For cache management informational purposes.
// Here we put some temporary stuff in the cache in order to let the first hit generate the page. The temporary cache will expire after a few seconds (typ. 30) or will be cleared by the rendered page, which will also clear and rewrite the cache.
$this->tempPageCacheContent();
if (!is_object($this->pages_lockObj) || $this->pages_lockObj->getLockStatus()==false) {
// Here we put some temporary stuff in the cache in order to let the first hit generate the page. The temporary cache will expire after a few seconds (typ. 30) or will be cleared by the rendered page, which will also clear and rewrite the cache.
$this->tempPageCacheContent();
}
// Setting cache_timeout_default. May be overridden by PHP include scritps.
$this->cacheTimeOutDefault = intval($this->config['config']['cache_period']);
......
$this->tempContent = false;
}
// Release open locks
$this->releasePageGenerationLock($this->pagesection_lockObj);
$this->releasePageGenerationLock($this->pages_lockObj);
// Sets sys-last-change:
$this->setSysLastChanged();
}
typo3/sysext/install/mod/class.tx_install.php (Arbeitskopie)
'typo3temp/llxml/' => array('This folder is part of the typo3temp/ section. It needs to be writable, too.',2,'dir_typo3temp'),
'typo3temp/cs/' => array('This folder is part of the typo3temp/ section. It needs to be writable, too.',2,'dir_typo3temp'),
'typo3temp/GB/' => array('This folder is part of the typo3temp/ section. It needs to be writable, too.',2,'dir_typo3temp'),
'typo3temp/locks/' => array('This folder is part of the typo3temp/ section. It needs to be writable, too.',2,'dir_typo3temp'),
'typo3conf/' => array('This directory contains the local configuration files of your website. TYPO3 must be able to write to these configuration files during setup and when the Extension Manager (EM) installs extensions.',2),
'typo3conf/ext/' => array('Location for local extensions. Must be writable if the Extension Manager is supposed to install extensions for this website.',0),
'typo3conf/l10n/' => array('Location for translations. Must be writable if the Extension Manager is supposed to install translations for extensions.',0),
(1-1/4)