fast_cache; } function __construct($bin, $options, $default_options) { // Assign the servers on the following order: bin specific -> default specific -> localhost port 11211 if (isset($options['servers'])) { $this->settings['servers'] = $options['servers']; $this->settings['compress'] = isset($options['compress']) ? TRUE : FALSE; $this->settings['shared'] = isset($options['shared']) ? $options['shared'] : TRUE; } else { if (isset($default_options['servers'])) { $this->settings['servers'] = $default_options['servers']; $this->settings['compress'] = isset($default_options['compress']) ? TRUE : FALSE; $this->settings['shared'] = isset($default_options['shared']) ? $default_options['shared'] : TRUE; } else { $this->settings['servers'] = array('localhost:11211'); $this->settings['compress'] = FALSE; $this->settings['shared'] = TRUE; } } parent::__construct($bin, $options, $default_options); $this->connect(); } function get($key) { // Attempt to pull from static cache. $cache = parent::get($this->key($key)); if (isset($cache)) { return $cache; } // Get from memcached $cache = $this->memcached->get($this->key($key)); // Update static cache parent::set($this->key($key), $cache); return $cache; } function set($key, $value, $expire = CACHE_PERMANENT, $headers = NULL) { if ($expire == CACHE_TEMPORARY) { $expire = 180; } // Create new cache object. $cache = new stdClass; $cache->cid = $key; $cache->created = REQUEST_TIME; $cache->expire = $expire; $cache->headers = $headers; $cache->data = $value; if (!empty($key)) { if ($this->settings['shared']) { if ($this->lock()) { // Get lookup table to be able to keep track of bins $lookup = $this->memcached->get($this->lookup); // If the lookup table is empty, initialize table if (!is_array($lookup)) { $lookup = array(); } // Set key to 1 so we can keep track of the bin $lookup[$this->key($key)] = 1; // Attempt to store full key and value if (!$this->memcached->set($this->key($key), $cache, $expire)) { unset($lookup[$this->key($key)]); $return = FALSE; } else { // Update static cache parent::set($this->key($key), $cache); $return = TRUE; } // Resave the lookup table (even on failure) $this->memcached->set($this->lookup, $lookup, $expire); // Remove lock. $this->unlock(); } } else { // Update memcached return $this->memcached->set($this->key($key), $cache, $expire); } } } function delete($key) { // Delete from static cache parent::flush(); if (substr($key, strlen($key) - 1, 1) == '*') { $key = $this->key(substr($key, 0, strlen($key) - 1)); if ($this->settings['shared']) { $lookup = $this->memcached->get($this->lookup); if (is_array($lookup) && !empty($lookup)) { foreach ($lookup as $k => $v) { if (substr($k, 0, strlen($key)) == $key) { $this->memcached->delete($k, 0); unset($lookup[$k]); } } } else { $lookup = array(); } if ($this->lock()) { $this->memcached->set($this->lookup, $lookup, 0); $this->unlock(); } } else { return $this->flush(); } } else { if (!empty($key)) { return $this->memcached->delete($this->key($key), 0); } } } function flush() { // Flush static cache parent::flush(); // If this is a shared cache, we need to cycle through the lookup table and remove individual // items directly if ($this->settings['shared']) { if ($this->lock()) { // Get lookup table to be able to keep track of bins $lookup = $this->memcached->get($this->lookup); // If the lookup table is empty, remove lock and return if (empty($lookup)) { $this->unlock(); return TRUE; } // Cycle through keys and remove each entry from the cache foreach ($lookup as $k => $v) { if ($this->memcached->delete($k, 0)) { unset($lookup[$k]); } } // Resave the lookup table (even on failure) $this->memcached->set($this->lookup, $lookup, 0); // Remove lock $this->unlock(); } } else { // Flush memcached return $this->memcached->flush(); } } function lock() { // Lock once by trying to add lock file, if we can't get the lock, we will loop // for 3 seconds attempting to get lock. If we still can't get it at that point, // then we give up and return FALSE. if ($this->memcached->add($this->lock, 1, 0) === FALSE) { $time = time(); while ($this->memcached->add($this->lock, 1, 0) === FALSE) { if (time() - $time >= 3) { return FALSE; } } } return TRUE; } function unlock() { return $this->memcached->delete($this->lock, 0); } function connect() { $this->memcached = new Memcached; $this->memcached->setOption(Memcached::OPT_COMPRESSION, $this->settings['compress']); foreach ($this->settings['servers'] as $server) { list($host, $port) = explode(':', $server); if (!$this->memcached->addServer($host, $port)) { watchdog('cache', "Unable to connect to memcached server $host:$port", WATCHDOG_ERROR); } } } function stats() { $memcached_stats = $this->memcached->getStats(); $stats = array( 'uptime' => $memcached_stats['uptime'], 'bytes_used' => $memcached_stats['bytes'], 'bytes_total' => $memcached_stats['limit_maxbytes'], 'gets' => $memcached_stats['cmd_get'], 'sets' => $memcached_stats['cmd_set'], 'hits' => $memcached_stats['get_hits'], 'misses' => $memcached_stats['get_misses'], 'req_rate' => (($memcached_stats['cmd_get'] + $memcached_stats['cmd_set']) / $memcached_stats['uptime']), 'hit_rate' => ($memcached_stats['get_hits'] / $memcached_stats['uptime']), 'miss_rate' => ($memcached_stats['get_misses'] / $memcached_stats['uptime']), 'set_rate' => ($memcached_stats['cmd_set'] / $memcached_stats['uptime']), ); return $stats; } }