]> git.mjollnir.org Git - moodle.git/commitdiff
$MCACHE - rework memcached support - some rework on eaccelerator
authormartinlanghoff <martinlanghoff>
Wed, 27 Dec 2006 22:47:51 +0000 (22:47 +0000)
committermartinlanghoff <martinlanghoff>
Wed, 27 Dec 2006 22:47:51 +0000 (22:47 +0000)
We now have a wrapper memcached support class to handle
initial connection setup, provide the common denominator
calls, plus getforfill() and friends.

The eaccelerator class now returns false to match memcached. The
downside of this is that we cannot store booleans as a false value
is indistinguishable from a false that indicates error or 'key not
found'.

lib/eaccelerator.class.php
lib/memcached.class.php [new file with mode: 0644]
lib/setuplib.php

index d07f0ee67b6dd5fa078a191f502e51d6680a4411..4f0b709f21dbefa3b34521660a9ad8042df3d345 100644 (file)
@@ -11,6 +11,9 @@
  **
  ** Author: Martin Langhoff <martin@catalyst.net.nz>
  **
+ ** Note: do NOT store booleans here. For compatibility with
+ ** memcached, a false value is indistinguisable from a 
+ ** "not found in cache" response.
  **/
 
 
@@ -52,7 +55,7 @@ class eaccelerator {
         $fn = $this->mode . '_get';
         $rec = $fn($this->prefix . $key);
         if (is_null($rec)) {
-            return null;
+            return false;
         }
         return unserialize($rec);
     } 
@@ -85,7 +88,7 @@ class eaccelerator {
      * http://marc.theaimsgroup.com/?l=git&m=116562052506776&w=2
      *
      * @param $key string
-     * @return mixed on cache hit, NULL otherwise
+     * @return mixed on cache hit, false otherwise
      */
     function getforfill ($key) {
         $get    = $this->mode . '_get';
@@ -98,7 +101,7 @@ class eaccelerator {
         if ($lock($this->prefix . $key . '_forfill')) {
             // we obtained the _forfill lock
             // our caller will compute and set the value
-            return null;
+            return false;
         }
         // someone else has the lock
         // "block" till we can get the value
@@ -110,7 +113,7 @@ class eaccelerator {
                 return unserialize($rec);
             }
         }
-        return null;
+        return false;
     }
 
     /**
diff --git a/lib/memcached.class.php b/lib/memcached.class.php
new file mode 100644 (file)
index 0000000..d58ce0c
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+/**
+ ** This class abstracts PHP's PECL memcached
+ ** API to provide
+ ** 
+ ** - get()
+ ** - set()
+ ** - delete()
+ ** - getforfill()
+ ** - releaseforfill()
+ **
+ ** Author: Martin Langhoff <martin@catalyst.net.nz>
+ **
+ ** Note: do NOT store booleans here. With memcached, a false value 
+ ** is indistinguisable from a "not found in cache" response. 
+ **/
+
+
+class memcached {
+
+    function memcached() {
+        global $CFG;
+
+        if (!function_exists('memcache_connect')) {
+            debugging("Memcached is set to true but the memcached extension is not installed");
+            return false;
+        }
+        $this->_cache = new Memcache;
+
+        $hosts = split(',', $CFG->memcachedhosts);
+        if (count($hosts) === 1 && !empty($CFG->memcachedpconn)) {
+            // the faster pconnect is only available
+            // for single-server setups
+            // NOTE: PHP-PECL client is buggy and pconnect() 
+            // will segfault if the server is unavailable
+            $this->_cache->pconnect($hosts[0]);
+        } else {
+            // multi-host setup will share key space
+            foreach ($hosts as $host) {
+                $host = trim($host);
+                $this->_cache->addServer($host);
+            }
+        }
+
+        $this->prefix = $CFG->dbname .'|' . $CFG->prefix . '|';
+    }
+
+    function status() {
+        if (is_object($this->_cache)) {
+            return true;
+        }
+        return false;
+    }
+
+    function set($key, $value, $ttl=0) {
+
+        // we may have acquired a lock via getforfill
+        // release if it exists
+        @$this->_cache->delete($this->prefix . $key . '_forfill');
+
+        return $this->_cache->set($this->prefix . $key, $value, false);
+    }
+
+    function get($key) {
+        $rec = $this->_cache->get($this->prefix . $key);
+        return $rec;
+    } 
+        
+    function delete($key) {
+        return $this->_cache->delete($this->prefix . $key);
+    }
+
+    /**
+     * In the simple case, this function will 
+     * get the cached value if available. If the entry
+     * is not cached, it will try to get an exclusive
+     * lock that announces that this process will
+     * populate the cache.
+     *
+     * If we fail to get the lock -- this means another
+     * process is doing it. 
+     * so we wait (block) for a few microseconds while we wait for
+     * the cache to be filled or the lock to timeout.
+     * 
+     * If you get a false from this call, you _must_
+     * populate the cache ASAP or indicate that
+     * you won't by calling releaseforfill().
+     *
+     * This technique forces serialisation and so helps deal 
+     * with thundering herd scenarios where a lot of clients 
+     * ask the for the same idempotent (and costly) operation. 
+     * The implementation is based on suggestions in this message
+     * http://marc.theaimsgroup.com/?l=git&m=116562052506776&w=2
+     *
+     * @param $key string
+     * @return mixed on cache hit, NULL otherwise
+     */
+    function getforfill ($key) {
+        
+        $rec = $this->_cache->get($this->prefix . $key);
+        if ($rec) {
+            return $rec;
+        }
+        if ($this->_cache->add($this->prefix . $key . '_forfill', 'true', false, 1)) {
+            // we obtained the _forfill lock
+            // our caller will compute and set the value
+            return false;
+        }
+        // someone else has the lock
+        // "block" till we can get the value
+        // actually, loop .05s waiting for it
+        for ($n=0;$n<5;$n++) {
+            usleep(10000);
+            $rec = $this->_cache->get($this->prefix . $key);
+            if ($rec) {
+                return $rec;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Release the exclusive lock obtained by 
+     * getforfill(). See getforfill()
+     * for more details.
+     *
+     * @param $key string
+     * @return bool
+     */
+    function releaseforfill ($key) {
+        return $this->_cache->delete($this->prefix . $key . '_forfill');
+    }
+}
+
+?>
\ No newline at end of file
index dd7a43fa4729afdb0fe8093ffc9bb67a52fe5806..db87e4ee618598127e7e71fc2dd3be3701ca71b7 100644 (file)
@@ -212,28 +212,13 @@ function setup_is_unicodedb() {
 function init_memcached() {
     global $CFG, $MCACHE;
 
-    if (!function_exists('memcache_connect')) {
-        debugging("Memcached is set to true but the memcached extension is not installed");
-        return false;
-    }
-                                               
-    $hosts = split(',', $CFG->memcachedhosts);
-    $MCACHE = new Memcache;
-    if (count($hosts) === 1 && !empty($CFG->memcachedpconn)) {
-        // the faster pconnect is only available
-        // for single-server setups
-        // NOTE: PHP-PECL client is buggy and pconnect() 
-        // will segfault if the server is unavailable
-        $MCACHE->pconnect($hosts[0]);
-    } else {
-        // multi-host setup will share key space
-        foreach ($hosts as $host) {
-            $host = trim($host);
-            $MCACHE->addServer($host);
-        }
-    }
-
-    return true;
+    include_once($CFG->libdir . '/memcached.class.php');
+    $MCACHE = new memcached;
+    if ($MCACHE->status()) {
+        return true;
+    } 
+    unset($MCACHE);
+    return false;                                           
 }
 
 function init_eaccelerator() {
@@ -241,7 +226,7 @@ function init_eaccelerator() {
 
     include_once($CFG->libdir . '/eaccelerator.class.php');
     $MCACHE = new eaccelerator;
-    if ($MCACHE->status) {
+    if ($MCACHE->status()) {
         return true;
     } 
     unset($MCACHE);