memcache.inc 8.22 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
      // 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.
36
      if (isset($memcached_prefixes[$this->bin]) && is_array($memcached_prefixes[$this->bin])) {
Jeremy's avatar
Jeremy committed
37 38 39 40 41 42 43 44 45 46 47 48 49
      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;
          }
        }
      }
50
      }
51 52 53 54 55 56
      $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)) ||
57 58
          (isset($cache_bins) && is_array($cache_bins) &&
          isset($cache_bins[$this->bin]) &&
59 60 61 62 63 64 65 66
          $cache_bins[$this->bin] > $cache->created)) {
        // Cache item expired, return NULL.
        return FALSE;
      }
      return $cache;
    }
    return FALSE;
  }
67

68 69 70 71 72 73 74 75 76 77 78 79
  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
80

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

Jeremy's avatar
Jeremy committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
    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];
      }
    }

105 106 107 108 109 110
    // 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
111
    $cache->counters = $counters;
112 113 114 115 116 117 118 119 120 121 122 123 124 125
    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;
    }
126

127 128 129 130 131
    // 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);
132
  }
133 134

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

137
    if (empty($cid) || $wildcard === TRUE) {
138 139 140 141 142 143 144 145 146 147 148
      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.
149
        if (isset($_SESSION['cache_flush']) && is_array($_SESSION['cache_flush'])) {
150 151 152 153 154 155 156 157 158
          $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
159 160 161 162 163 164 165 166 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
        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);
208 209 210 211 212 213
      }
    }
    else {
      $cids = is_array($cid) ? $cid : array($cid);
      foreach ($cids as $cid) {
        dmemcache_delete($cid, $this->bin, $this->memcache);
214 215 216
      }
    }
  }
217

218 219 220
  function isEmpty() {
    // We do not know so err on the safe side?
    return FALSE;
221 222
  }
}
223