|
Smarty
WARNING: All discussion is moving to https://reddit.com/r/smarty, please go there! This forum will be closing soon. |
|
View previous topic :: View next topic |
Author |
Message |
kowach Smarty Rookie
Joined: 26 Jan 2011 Posts: 13
|
Posted: Mon Aug 29, 2011 7:19 pm Post subject: APC cache plugin with with READ/WRITE LOCK mechanism |
|
|
Hi,
I think that all smarty cache plugins lack of lock read mechanism. Problem ocures when multiple apache threads try to read expired cached template. Or wen you clean all cache (happens on httpd restart with apc)
Simple example:
we have 100 parallel request on one resource. Executing that resource is very costly. If it's not cached first process will generate the cache, but if generating cache takes to long, second process will start generating that cache again, third the same... And every next will take some CPU power and slow down others theads until CPU load goes to 100.0 on quad core processor
My class is based on Monte Ohrt APC cache mechanism.
I added some modifications. Lock is put when calling
$smarty->isCached( "myTemplate.tpl", "my-cache-id-73" )
and is released on
$smarty->display( "myTemplate.tpl", "my-cache-id-73" ) or $smarty->fetch(...) or on cache object destruction or after maxLockTime (default 30sec)
In this way first thread locks template and all other 99 must wait. Waiting cost almost nothing CPU, so first thread will have enough CPU to process costly resource (ex. some bad ass mysql query).
Code: |
<?php
/**
* Smarty Plugin CacheResource APC
*
* Implements APC resource for the HTML cache
*
* Work with Smarty 3.0.x
* Does not works with Smarty 3.1.x
*
* @package Smarty
* @subpackage Cacher
* @author Monte Ohrt (APC base)
* @author Vladimir Kovacevic (Upgrade on lock mechanisam)
*/
/**
* This class does contain all necessary methods for the HTML cache with APC
*/
class Smarty_CacheResource_Apc {
/**
* Max lock/wait time - if something goes wrong (eg. lock not canceled)
*
* @var int
*/
private $maxLockTime = 30;
/**
* Enable locking
*
* @var boolean
*/
private $lockEnable = true;
/**
* Global var name for temporary key storage
*
* @var string
*/
private $lockKeyVar = 'SMARTYAPCLOCK';
function __construct($smarty)
{
$this->smarty = $smarty;
// test if APC is present
if(!function_exists('apc_cache_info'))
throw new Exception('APC Template Caching Error: APC is not installed');
}
/**
* Returns the filepath of the cached template output
*
* @param object $_template current template
* @return string the cache filepath
*/
public function getCachedFilepath($_template)
{
$tmp = $_template->getTemplateFilepath();
if(isset($_template->cache_id)) $tmp.=$_template->cache_id;
if(isset($_template->compile_id)) $tmp.=$_template->compile_id;
return ($tmp);
}
/**
* Returns the timpestamp of the cached template output
*
* WARING if lock is enabled this will lock template with this CacheID for reading:
* $smarty->isCached( "myTemplate.tpl", "cache-id-73" )
*
* @param object $_template current template
* @return integer |booelan the template timestamp or false if the file does not exist
*/
public function getCachedTimestamp($_template)
{
# Is template lock?
if($this->lockEnable)
{
$lockKey = "[LOCK]".$this->getCachedFilepath($_template);
apc_fetch($lockKey, $locked);
}
$i=0;
# Wait! Cache is generated by other script
while ($this->lockEnable && $locked===true)
{
$i++;
sleep(1);
apc_fetch($lockKey, $locked);
}
# Try to fetch cached content...
apc_fetch($this->getCachedFilepath($_template), $success);
$ret = $success ? time() : false;
# Cache does not exists -> LOCK the template -> we will probably generate cache later
if($this->lockEnable && $ret===false && $_template->cache_lifetime>0)
{
apc_store($lockKey, 1, $this->maxLockTime);
if(!isset($GLOBALS[$this->lockKeyVar])) $GLOBALS[$this->lockKeyVar]=array();
# Remember all lock keys
$GLOBALS[$this->lockKeyVar][]=$lockKey;
}
return $ret;
}
/**
* Returns the cached template output
*
* @param object $_template current template
* @return string |booelan the template content or false if the file does not exist
*/
public function getCachedContents($_template)
{
$_cache_content = apc_fetch($this->getCachedFilepath($_template));
$_smarty_tpl = $_template;
ob_start();
$er = error_reporting();
error_reporting(E_ALL^E_NOTICE);
eval("?>" . $_cache_content);
error_reporting($er);
return ob_get_clean();
}
/**
* Writes the rendered template output to cache file
*
* @param object $_template current template
* @return boolean status
*/
public function writeCachedContent($_template, $content)
{
if($_template->cache_lifetime>0)
{
$ret = apc_store($this->getCachedFilepath($_template), $content, $_template->cache_lifetime);
# We have stored cache, now release the Lock
if( $this->lockEnable )
{
$lockKey = "[LOCK]".$this->getCachedFilepath($_template);
apc_delete($lockKey);
unset($GLOBALS[$this->lockKeyVar][$lockKey]);
}
return $ret;
}
}
/**
* Empty cache folder
*
* @param integer $exp_time expiration time
* @return integer number of cache files deleted
*/
public function clearAll($exp_time = null)
{
return apc_clear_cache('user');
}
/**
* Empty cache for a specific template
*
* @param string $resource_name template name
* @param string $cache_id cache id
* @param string $compile_id compile id
* @param integer $exp_time expiration time
* @return integer number of cache files deleted
*/
public function clear($resource_name, $cache_id, $compile_id, $exp_time)
{
return apc_delete($this->smarty->template_dir[0].'/'.$resource_name.$cache_id.$compile_id);
}
/**
* Clear all global locks if writeCachedContent was not called
*/
public function __destruct()
{
if($this->lockEnable && isset($GLOBALS[$this->lockKeyVar])) {
foreach ($GLOBALS[$this->lockKeyVar] as $lockKey) {
apc_delete($lockKey);
}
unset($GLOBALS[$this->lockKeyVar]);
}
}
}
|
|
|
Back to top |
|
mohrt Administrator
Joined: 16 Apr 2003 Posts: 7368 Location: Lincoln Nebraska, USA
|
Posted: Mon Aug 29, 2011 8:58 pm Post subject: |
|
|
Code: | # Wait! Cache is generated by other script
while ($this->lockEnable && $locked===true)
{
$i++;
sleep(1);
apc_fetch($lockKey, $locked);
} |
Is this a possible deadlock? That is the problems with most locking mechanisms, they can make a deadlock situation, such as when the thread that does the locking crashes unexpectedly. |
|
Back to top |
|
kowach Smarty Rookie
Joined: 26 Jan 2011 Posts: 13
|
Posted: Mon Aug 29, 2011 9:10 pm Post subject: |
|
|
mohrt wrote: | Code: | # Wait! Cache is generated by other script
while ($this->lockEnable && $locked===true)
{
$i++;
sleep(1);
apc_fetch($lockKey, $locked);
} |
Is this a possible deadlock? That is the problems with most locking mechanisms, they can make a deadlock situation, such as when the thread that does the locking crashes unexpectedly. |
No, there are 3 things that care about unlock. Last thing that would happen is automatic expiration of APC key $lockKey after 30 seconds and $lock is set to false. |
|
Back to top |
|
mohrt Administrator
Joined: 16 Apr 2003 Posts: 7368 Location: Lincoln Nebraska, USA
|
Posted: Mon Aug 29, 2011 9:37 pm Post subject: |
|
|
I see APC automatically expires it. I'd like to see this work for any type of cache resource, we are looking into it. |
|
Back to top |
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|