diff --git a/lib/eaccelerator.class.php b/lib/eaccelerator.class.php index d07f0ee67b6..4f0b709f21d 100644 --- a/lib/eaccelerator.class.php +++ b/lib/eaccelerator.class.php @@ -11,6 +11,9 @@ ** ** Author: Martin Langhoff ** + ** 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 index 00000000000..d58ce0c334f --- /dev/null +++ b/lib/memcached.class.php @@ -0,0 +1,135 @@ + + ** + ** 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 diff --git a/lib/setuplib.php b/lib/setuplib.php index dd7a43fa472..db87e4ee618 100644 --- a/lib/setuplib.php +++ b/lib/setuplib.php @@ -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);