memcache.inc 8.69 KB
Newer Older
1
<?php
robertDouglass's avatar
robertDouglass committed
2
// $Id$
3

4
require_once 'dmemcache.inc';
5 6 7

/** Implementation of cache.inc with memcache logic included **/

8 9 10 11 12 13
class MemCacheDrupal implements DrupalCacheInterface {
  function __construct($bin) {
    $this->memcache = dmemcache_object($bin);
    $this->bin = $bin;
  }
  function get($cid) {
Jeremy's avatar
Jeremy committed
14 15 16 17 18 19 20
    global $memcached_prefixes, $memcached_counters;
    if (!isset($memcached_prefixes)) {
      $memcached_prefixes = array();
    }
    if (!isset($memcached_counters)) {
      $memcached_counters = array();
    }
21 22 23 24 25
    // Determine when the current bin was last flushed.
    $cache_flush = variable_get("cache_flush_$this->bin", 0);
    // Retrieve the item from the cache.
    $cache = dmemcache_get($cid, $this->bin, $this->memcache);
    if (is_object($cache)) {
Jeremy's avatar
Jeremy committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
      // Load the prefix directory.
      if (!isset($memcached_prefixes[$this->bin])) {
        $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
        if ($memcached_prefixes[$this->bin] === FALSE) {
          $memcached_prefixes[$this->bin] = array();
        }
        $memcached_counters[$this->bin] = array();
      }

      // Check if the item being fetched matches any prefixes.
      foreach ($memcached_prefixes[$this->bin] as $prefix) {
        if (substr($cid, 0, strlen($prefix)) == $prefix) {
          // On a match, check if we already know the current counter value.
          if (!isset($memcached_counters[$this->bin][$prefix])) {
            $memcached_counters[$this->bin][$prefix] = dmemcache_get('.prefix.' . $prefix, $this->bin);
          }

          // If a matching prefix for this item was cleared after storing it, it is invalid.
          if (!isset($cache->counters[$prefix]) || $cache->counters[$prefix] < $memcached_counters[$this->bin][$prefix]) {
            return 0;
          }
        }
      }
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
      $cache_bins = isset($_SESSION['cache_flush']) ? $_SESSION['cache_flush'] : NULL;
      // Items cached before the cache was last flushed are no longer valid.
      $cache_lifetime = variable_get('cache_lifetime', 0);
      if ($cache_lifetime && $cache->created && $cache_flush &&
          ($cache->created < $cache_flush) &&
          ((time() - $cache->created >= $cache_lifetime)) ||
          (is_array($cache_bins) && $cache_bins[$this->bin] &&
          $cache_bins[$this->bin] > $cache->created)) {
        // Cache item expired, return NULL.
        return FALSE;
      }
      return $cache;
    }
    return FALSE;
  }
64

65 66 67 68 69 70 71 72 73 74 75 76
  function getMultiple(&$cids) {
    // With cache flushes and stampede protection it's impossible to do use
    // the multiple get. Pity.
    $results = array();
    foreach ($cids as $key => $cid) {
      if ($result = $this->get($cid)) {
        $results[$cid] = $result;
        unset($cids[$key]);
      }
    }
    return $results;
  }
Steve Rude's avatar
Steve Rude committed
77

78
  function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL) {
Jeremy's avatar
Jeremy committed
79
    global $memcached_prefixes, $memcached_counters;
80
    $created = time();
81

Jeremy's avatar
Jeremy committed
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
    if (!isset($memcached_prefixes[$this->bin])) {
      $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
      if ($memcached_prefixes[$this->bin] === FALSE) {
        $memcached_prefixes[$this->bin] = array();
      }
      $memcached_counters[$this->bin] = array();
    }

    $counters = array();
    // Check if the item being stored matches any prefixes.
    foreach ($memcached_prefixes[$this->bin] as $prefix) {
      if (substr($cid, 0, strlen($prefix)) == $prefix) {
        // On a match, check if we already know the current counter value.
        if (!isset($memcached_counters[$this->bin][$prefix])) {
          $memcached_counters[$this->bin][$prefix] = dmemcache_get('.prefix.' . $prefix, $this->bin);
        }
        $counters[$prefix] = $memcached_counters[$this->bin][$prefix];
      }
    }

102 103 104 105 106 107
    // Create new cache object.
    $cache = new stdClass;
    $cache->cid = $cid;
    $cache->data = is_object($data) ? clone $data : $data;
    $cache->created = $created;
    $cache->headers = $headers;
Jeremy's avatar
Jeremy committed
108
    $cache->counters = $counters;
109 110 111 112 113 114 115 116 117 118 119 120 121 122
    if ($expire == CACHE_TEMPORARY) {
      // Convert CACHE_TEMPORARY (-1) into something that will live in memcache
      // until the next flush.
      $cache->expire = time() + 2591999;
    }
    // Expire time is in seconds if less than 30 days, otherwise is a timestamp.
    else if ($expire != CACHE_PERMANENT && $expire < 2592000) {
      // Expire is expressed in seconds, convert to the proper future timestamp
      // as expected in dmemcache_get().
      $cache->expire = time() + $expire;
    }
    else {
      $cache->expire = $expire;
    }
123

124 125 126 127 128
    // We manually track the expire time in $cache->expire.  When the object
    // expires, we only allow one request to rebuild it to avoid cache stampedes.
    // Other requests for the expired object while it is still being rebuilt get
    // the expired object.
    dmemcache_set($cid, $cache, 0, $this->bin, $this->memcache);
129
  }
130 131

  function clear($cid = NULL, $wildcard = FALSE) {
Jeremy's avatar
Jeremy committed
132 133
    global $memcached_prefixes, $memcached_counters;

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    // Default behavior for when cache_clear_all() is called without parameters
    // is to clear all of the expirable entries in the block and page caches.
    if (empty($cid) || ($cid == '*' && $wildcard !== TRUE)) {
      // don't do anything if cid is unset. this matches the default drupal behavior...
      if ($wildcard && $cid != '*') {
        if (variable_get('memcache_debug', FALSE)) {
          // call watchdog, since you probably didn't want to flush the entire bin.
          watchdog('memcache', "illegal wildcard in cache_clear_all - not flushing entire bin. bin: $this->bin. cid: $cid", WATCHDOG_WARNING);
        }
      }
    }
    else if ($cid == '*' || $wildcard === TRUE) {
      if (variable_get('cache_lifetime', 0)) {
        // Update the timestamp of the last global flushing of this bin.  When
        // retrieving data from this bin, we will compare the cache creation
        // time minus the cache_flush time to the cache_lifetime to determine
        // whether or not the cached item is still valid.
        variable_set("cache_flush_$this->bin", time());

        // We store the time in the current user's session which is saved into
        // the sessions table by sess_write().  We then simulate that the cache
        // was flushed for this user by not returning cached data to this user
        // that was cached before the timestamp.
        if (is_array($_SESSION['cache_flush'])) {
          $cache_bins = $_SESSION['cache_flush'];
        }
        else {
          $cache_bins = array();
        }
        $cache_bins[$this->bin] = time();
        $_SESSION['cache_flush'] = $cache_bins;
      }
      else {
Jeremy's avatar
Jeremy committed
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
        if ($cid == '*') {
          $cid = '';
        }

        // Get a memcached object for complex operations.
        $mc = dmemcache_object($this->bin);

        // Load the prefix directory.
        if (!isset($memcached_prefixes[$this->bin])) {
          $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
          if ($memcached_prefixes[$this->bin] === FALSE) {
            $memcached_prefixes[$this->bin] = array();
          }
        }

        // Ensure the prefix being cleared is listed, if not, atomically add it.
        // Adding new prefixes should be rare.
        if (!in_array($cid, $memcached_prefixes[$this->bin])) {
          // Acquire a semaphore.
          $lock_key = dmemcache_key('.prefixes.lock', $this->bin);
          while (!$mc->add($lock_key, 1, FALSE, 10)) {
            usleep(1000);
          }

          // Get a fresh copy of the prefix directory.
          $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
          if ($memcached_prefixes[$this->bin] === FALSE) {
            $memcached_prefixes[$this->bin] = array();
          }

          // Only add the prefix if it's not in the updated directory.
          if (!in_array($cid, $memcached_prefixes[$this->bin])) {
            // Add the new prefix.
            $memcached_prefixes[$this->bin][] = $cid;

            // Store to memcached.
            dmemcache_set('.prefixes', $memcached_prefixes[$this->bin], 0, $this->bin);

            // Set the clearing counter to zero.
            dmemcache_set('.prefix.' . $cid, 0, 0, $this->bin);
          }

          // Release the semaphore.
          dmemcache_delete('.prefixes.lock', $this->bin);
        }

        // Increment the prefix clearing counter.
        $counter_key = dmemcache_key('.prefix.' . $cid, $this->bin);
        $memcached_counters[$this->bin][$cid] = $mc->increment($counter_key);
216 217 218 219 220 221
      }
    }
    else {
      $cids = is_array($cid) ? $cid : array($cid);
      foreach ($cids as $cid) {
        dmemcache_delete($cid, $this->bin, $this->memcache);
222 223 224
      }
    }
  }
225 226 227 228
  
  function isEmpty() {
    // We do not know so err on the safe side?
    return FALSE;
229 230
  }
}
231