ZF-10522: Zend_Cache_Backend_TwoLevels should fallback to slowBackend if fastBackend is unavailable

Description

When using Zend_Cache like


// set up caching for query results (could be used for other things as well)
$fO = array('lifetime' => 86400, 'automatic_serialization' => true); // cache of 1 day
$bO = array(
    'slow_backend' => 'File',
    'fast_backend' => 'Memcached',
    'slow_backend_options' => array('cache_dir' => '/tmp/ads'),
    'fast_backend_options' => array('servers' =>
        array(
            array('host' => 'hostname', 'port' => 11211, 'persistent' => true, 'failure_callback' => 'memcache_server_failure_callback')
        ),
    ),
    'stats_update_factor' => 0,
);
$cache = Zend_cache::factory('Core', 'Two Levels', $fO, $bO);

If my memcached server becomes unavailable (I stop the service, etc), then the call to $cache->load() fails, stating that it can't stat :.

I believe this should be reworked so that if the connection cannot be made, then it falls back to slowBackend. I've attached code that should do just this.


--- Zend/Cache/Backend/TwoLevels.php    2010-10-06 14:38:46.000000000 -0400
+++ /home/glens/TwoLevels.php   2010-10-06 14:38:33.000000000 -0400
@@ -196,7 +197,7 @@
        try {
            $res = $this->_fastBackend->load($id, $doNotTestCacheValidity);
        } catch (Exception $e) {
+           $res = false;
        }
         if ($res === false) {
             $res = $this->_slowBackend->load($id, $doNotTestCacheValidity);

@@ -489,19 +491,31 @@
         if ($mode == 'saving') {
             // mode saving
             if ($this->_fastBackendFillingPercentage === null) {
-                $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+                try {
+                    $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+                } catch (Exception $e) {
+                    $this->_fastBackendFillingPercentage = false;
+                }
             } else {
                 $rand = rand(1, $this->_options['stats_update_factor']);
                 if ($rand == 1) {
                     // we force a refresh
-                    $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+                    try {
+                        $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+                    } catch (Exception $e) {
+                        $this->_fastBackendFillingPercentage = false;
+                    }
                 }
             }
         } else {
             // mode loading
             // we compute the percentage only if it's not available in cache
             if ($this->_fastBackendFillingPercentage === null) {
-                $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+                try {
+                    $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+                } catch (Exception $e) {
+                    $this->_fastBackendFillingPercentage = false;
+                }
             }
         }
         return $this->_fastBackendFillingPercentage;

Comments

Here's my solution. Test the connection to memcached if cache is configured to use it. If the connection fails, set the cache manager's template options to use File backend. I'm setting up my database cache in the app config and using those settings here.


// test memecache connection
$config = Zend_Registry::get('config');
$backend = $config->resources->cachemanager->database->backend;
$memcacheConnected = false;
if ($backend->name === 'Memcached') {
    $mcHost = 'localhost';
    $mcPort = 11211;
    if (isset($backend->options->servers->one) && $server = $backend->options->servers->one) {
        $server->host && $mcHost = $server->host;
        $server->port && $mcPort = $server->port;
    }
    $err = error_reporting(E_ERROR);
    $memcache = new Memcache;  
    if (!$memcacheConnected = $memcache->connect($mcHost, $mcPort)) {
        error_log("Memcache: Failed to connect to memcached at $mcHost:$mcPort");
        //use File cache if Memcache connection fails
        $cachemanager->setTemplateOptions('database', array('backend' => array('name' => 'File')));
    }
            
    error_reporting($err);
}
$cache = $cachemanager->getCache('database');