--- memcache.inc	2010-05-26 17:33:50.899423027 -0400
+++ /var/www/memcache7/sites/default/modules/memcache/memcache.inc	2010-08-20 11:54:35.257385999 -0400
@@ -18,58 +18,31 @@
   function getMultiple(&$cids) {
     $results = dmemcache_get_multi($cids, $this->bin, $this->memcache);
     foreach ($results as $cid => $result) {
-      if (!$this->valid($cid, $result)) {
+      if (!$this->valid($result->cid, $result)) {
         // This object has expired, so don't return it.
         unset($results[$cid]);
       }
       else {
         // Remove items from the referenced $cids array that we are returning,
         // per the comment in cache_get_multiple() in includes/cache.inc.
-        unset($cids[$cid]);
+        unset($cids[$result->cid]);
       }
     }
     return $results;
   }
 
   protected function valid($cid, $cache) {
-    global $memcached_prefixes, $memcached_counters;
-    if (!isset($memcached_prefixes)) {
-      $memcached_prefixes = array();
-    }
-    if (!isset($memcached_counters)) {
-      $memcached_counters = array();
+    if (!is_object($cache)) {
+      return FALSE;
     }
 
-    if (!is_object($cache)) {
+    if (!$this->wildcard_valid($cid, $cache)) {
       return FALSE;
     }
 
     // Determine when the current bin was last flushed.
     $cache_flush = variable_get("cache_flush_$this->bin", 0);
 
-    // Load the prefix directory.
-    if (!isset($memcached_prefixes[$this->bin])) {
-      $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
-      if (!is_array($memcached_prefixes[$this->bin])) {
-        $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 FALSE;
-        }
-      }
-    }
-
     $cache_lifetime = variable_get('cache_lifetime', 0);
     $item_flushed_globally = $cache->created && $cache_flush && $cache_lifetime && ($cache->created < min($cache_flush, time() - $cache_lifetime));
 
@@ -99,28 +72,10 @@
   }
 
   function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL) {
-    global $memcached_prefixes, $memcached_counters;
     $created = time();
 
-    if (!isset($memcached_prefixes[$this->bin]) || !is_array($memcached_prefixes[$this->bin])) {
-      $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
-      if (!is_array($memcached_prefixes[$this->bin])) {
-        $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];
-      }
-    }
+    // Check if the item being stored matches any wildcards.
+    $counters = $this->wildcard_matches($cid);
 
     // Create new cache object.
     $cache = new stdClass;
@@ -152,10 +107,9 @@
   }
 
   function clear($cid = NULL, $wildcard = FALSE) {
-    global $memcached_prefixes, $memcached_counters;
 
     if (empty($cid) || $wildcard === TRUE) {
-      if (variable_get('cache_lifetime', 0)) {
+      if (variable_get('cache_lifetime', 0) && empty($cid)) {
         // 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
@@ -179,60 +133,210 @@
         if ($cid == '*') {
           $cid = '';
         }
+        $this->wildcard_clear($cid);
+      }
+    }
+    else {
+      $cids = is_array($cid) ? $cid : array($cid);
+      foreach ($cids as $cid) {
+        dmemcache_delete($cid, $this->bin, $this->memcache);
+      }
+    }
+  }
 
-        // Get a memcached object for complex operations.
-        $mc = dmemcache_object($this->bin);
+  /**
+   * Hash/chunk the cid so we don't have a single lock for the entire bin.
+   * Adaptive hashing -- the longer the $cid, the more we skip characters in
+   * order to spread out where we're taking samples from.
+   */
+  private function hash_cid($cid) {
+    static $hashes = array();
+    // The memcache_hashsize allows us to configure the number of cid characters
+    // used to build a hash.  If working with a lot of large cids and wildcard
+    // flushes you may need to increase this to minimize memory consumption and
+    // lock contention.
+    $hashsize = (int)variable_get('memcache_hashsize', 6);
+    if ($hashsize % 3) {
+      // Must be evenly divisible by three.
+      $hashsize = 6;
+    }
+    if (!isset($hashes[$cid])) {
+      $values = '';
+      $inc = ceil(strlen($cid) / $hashsize);
+      for ($i = 0; $i < $hashsize; $i++) {
+        $key = $i * $inc;
+        $values .= isset($cid[$key]) ? $cid[$key] : '.';
+      }
+      $hashes[$cid] = explode(',', chunk_split($values, $hashsize / 3, ','));
+    }
+    return $hashes[$cid];
+  }
 
-        // Load the prefix directory.
-        if (!isset($memcached_prefixes[$this->bin])) {
-          $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
-          if (!is_array($memcached_prefixes[$this->bin])) {
-            $memcached_prefixes[$this->bin] = array();
-          }
+  /**
+   * Determine all possible hashes that could match our cid.
+   */
+  private function multihash_cid($cid) {
+    static $hashes = array();
+    if (!isset($hashes[$cid])) {
+      $subcid_last = NULL;
+      for ($i = 1; $i <= strlen($cid); $i++) {
+        $subcid = substr($cid, 0, $i);
+        $subhash = $this->hash_cid($subcid);
+        // Return only unique hash combinations
+        if (!$subcid_last || !isset($hashes[$cid]) || !isset($hashes[$cid][$subcid_last]) || $hashes[$cid][$subcid_last] !== $subhash) {
+          $hashes[$cid][$subcid] = $subhash;
         }
+        $subcid_last = $subcid;
+      }
+    }
+    return $hashes[$cid];
+  }
 
-        // 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 (!dmemcache_add($lock_key, 1, 10)) {
-            usleep(1000);
-          }
+  /**
+   * Get current record of wildcard flushes.  If cid is set, add a new one.
+   */
+  private function wildcards($hash, $cid = NULL) {
+    static $wildcards = array();
+
+    // Load the appropriate wildcard directory
+    if (!isset($wildcards[$this->bin]) ||
+        !isset($wildcards[$this->bin][$hash[0]]) ||
+        !isset($wildcards[$this->bin][$hash[0]][$hash[1]]) ||
+        !isset($wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]])) {
+      $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] = dmemcache_get('.wildcard-' . $this->bin . $hash[0] . $hash[1] . $hash[2], $this->bin);
+      if (!is_array($wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]])) {
+        $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] = array();
+      }
+    }
 
-          // Get a fresh copy of the prefix directory.
-          $memcached_prefixes[$this->bin] = dmemcache_get('.prefixes', $this->bin);
-          if (!is_array($memcached_prefixes[$this->bin])) {
-            $memcached_prefixes[$this->bin] = array();
-          }
+    if (isset($cid)) {
+      $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]][] = $cid;
+    }
 
-          // 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;
+    return $wildcards;
+  }
 
-            // Store to memcached.
-            dmemcache_set('.prefixes', $memcached_prefixes[$this->bin], 0, $this->bin);
+  /**
+   * See if cid matches an existing wildcard flush.
+   */
+  private function wildcard_matches($cid) {
+    $counters = array();
+    $wildcard_counters = $this->wildcard_counters();
+    $multihash = $this->multihash_cid($cid);
 
-            // Set the clearing counter to zero.
-            dmemcache_set('.prefix.' . $cid, 0, 0, $this->bin);
+    // TODO: Optimize this with a multi-get
+    foreach ($multihash as $hash) {
+      $wildcards = $this->wildcards($hash);
+      // Check if the item being stored matches any wildcards.
+      foreach ($wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] as $wildcard) {
+        if (substr($cid, 0, strlen($wildcard)) == $wildcard) {
+          // On a match, check if we already know the current counter value.
+          if (!isset($wildcard_counters[$this->bin][$wildcard])) {
+            $wildcard_counters[$this->bin][$wildcard] = dmemcache_get(".wildcard.$wildcard", $this->bin);
+            $this->wildcard_counters($wildcard_counters);
           }
+          $counters[$wildcard] = $wildcard_counters[$this->bin][$wildcard];
+        }
+      }
+    }
+    return $counters;
+  }
+
+  /**
+   * Perform a wildcard clear, updating the correct counters and hash bin.
+   */
+  private function wildcard_clear($cid) {
+    $hash = $this->hash_cid($cid);
+    $wildcards = $this->wildcards($hash);
+
+    if (!is_array($wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]])) {
+      $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] = array();
+    }
+
+    $lock_acquire_attempts = 0;
+
+    // Ensure the wildcard being cleared is listed, if not, atomically add it.
+    do {
+      if (!in_array($cid, $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]])) {
+        $lock_name = 'memcache.wildcards:' . $this->bin . $hash[0] . $hash[1] . $hash[2];
+        if (!$lock_acquired = lock_acquire($lock_name)) {
+          lock_wait();
+        }
 
-          // Release the semaphore.
-          dmemcache_delete('.prefixes.lock', $this->bin);
+        // Get a fresh copy of the wildcard directory now that we own the lock.
+        $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] = dmemcache_get('.wildcard-' . $this->bin . $hash[0] . $hash[1] . $hash[2], $this->bin);
+        if (!is_array($wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]])) {
+          $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] = array();
         }
 
-        // Increment the prefix clearing counter.
-        $counter_key = dmemcache_key('.prefix.' . $cid, $this->bin);
-        $memcached_counters[$this->bin][$cid] = $mc->increment($counter_key);
+        // Only add the wildcard if it's not in the updated directory.
+        if (!in_array($cid, $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]])) {
+          // Add the new wildcard.
+          $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]][] = $cid;
+
+          // Store to memcached.
+          dmemcache_set('.wildcard-' . $this->bin . $hash[0] . $hash[1] . $hash[2], $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]], 0, $this->bin);
+          $this->wildcards($hash, $cid);
+
+          // Set the clearing counter to zero.
+          dmemcache_set(".wildcard.$cid", '0', 0, $this->bin);
+        }
+        lock_release($lock_name);
       }
+    } while (in_array($cid, $wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]]) && $lock_acquired === FALSE && $lock_acquire_attempts++ < 5);
+
+    // Increment statically cached counters.
+    $this->wildcard_counters(NULL, $this->bin, $cid);
+  }
+
+  /**
+   * Track flush counters.  If bin and cid are set, increment a counter.
+   */
+  private function wildcard_counters($counters = NULL, $bin = NULL, $cid = NULL) {
+    static $wildcard_counters = array();
+
+    if (!empty($counters) && is_array($counters)) {
+      $wildcard_counters = $counters;
+    }
+    if (!is_null($bin) && !is_null($cid)) {
+      // Increment the wildcard clearing counter.
+      $mc = dmemcache_object($this->bin);
+      $counter_key = dmemcache_key(".wildcard.$cid", $this->bin);
+      $mc->increment($counter_key);
+      $wildcard_counters[$this->bin][$cid]++;
     }
-    else {
-      $cids = is_array($cid) ? $cid : array($cid);
-      foreach ($cids as $cid) {
-        dmemcache_delete($cid, $this->bin, $this->memcache);
+    return $wildcard_counters;
+  }
+
+  /**
+   * Determine if cid is valid, depends on if it matches a wildcard flush.
+   * TODO: Can this logic be combined with wildcard_matches?
+   */
+  private function wildcard_valid($cid, $cache) {
+    $wildcard_counters = $this->wildcard_counters($cid);
+
+    $multihash = $this->multihash_cid($cid);
+    // TODO: Optimize this with a multi-get
+    foreach ($multihash as $hash) {
+      $wildcards = $this->wildcards($hash);
+
+      // Check if the item being fetched matches any wildcards.
+      foreach ($wildcards[$this->bin][$hash[0]][$hash[1]][$hash[2]] as $wildcard) {
+        if (substr($cid, 0, strlen($wildcard)) == $wildcard) {
+          // On a match, check if we already know the current counter value.
+          if (!isset($wildcard_counters[$this->bin][$wildcard])) {
+            $wildcard_counters[$this->bin][$wildcard] = dmemcache_get(".wildcard.$wildcard", $this->bin);
+            $this->wildcard_counters($wildcard_counters);
+          }
+          // If a matching wildcard for this item was cleared after storing it,
+          // it is invalid.
+          if (!isset($cache->counters[$wildcard]) || $cache->counters[$wildcard] < $wildcard_counters[$this->bin][$wildcard]) {
+            return FALSE;
+          }
+        }
       }
     }
+    return TRUE;
   }
 
   function isEmpty() {
