Smarty Forum Index Smarty
WARNING: All discussion is moving to https://reddit.com/r/smarty, please go there! This forum will be closing soon.

APC cache plugin with with READ/WRITE LOCK mechanism

 
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.    Smarty Forum Index -> Plugins
View previous topic :: View next topic  
Author Message
kowach
Smarty Rookie


Joined: 26 Jan 2011
Posts: 13

PostPosted: Mon Aug 29, 2011 7:19 pm    Post subject: APC cache plugin with with READ/WRITE LOCK mechanism Reply with quote

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 Smile

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
View user's profile Send private message
mohrt
Administrator


Joined: 16 Apr 2003
Posts: 7368
Location: Lincoln Nebraska, USA

PostPosted: Mon Aug 29, 2011 8:58 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
kowach
Smarty Rookie


Joined: 26 Jan 2011
Posts: 13

PostPosted: Mon Aug 29, 2011 9:10 pm    Post subject: Reply with quote

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
View user's profile Send private message
mohrt
Administrator


Joined: 16 Apr 2003
Posts: 7368
Location: Lincoln Nebraska, USA

PostPosted: Mon Aug 29, 2011 9:37 pm    Post subject: Reply with quote

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
View user's profile Send private message Visit poster's website
Display posts from previous:   
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.    Smarty Forum Index -> Plugins All times are GMT
Page 1 of 1

 
Jump to:  
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
Protected by Anti-Spam ACP