Index: README.txt
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/memcache/README.txt,v
retrieving revision 1.4.2.10.2.4
retrieving revision 1.4.2.10.2.5
diff -u -r1.4.2.10.2.4 -r1.4.2.10.2.5
--- README.txt	25 Jan 2009 04:08:34 -0000	1.4.2.10.2.4
+++ README.txt	29 Jan 2009 18:01:47 -0000	1.4.2.10.2.5
@@ -1,4 +1,4 @@
-// $Id: README.txt,v 1.4.2.10.2.4 2009/01/25 04:08:34 jvandyk Exp $
+// $Id: README.txt,v 1.4.2.10.2.5 2009/01/29 18:01:47 jeremy Exp $
 
 ## INSTALLATION ##
 
@@ -35,8 +35,8 @@
 have enough memory allocated to memcache to store everything (including the page
 cache), otherwise the cache misses will negate the benefit of the cache hits.
 
-Note that memcache.db.inc supports minimum cache lifetime settings while
-memcache.inc does not. For the difference, see these diagrams:
+Note that memcache.db.inc support a global minimum cache lifetime, whereas
+memcache.inc tracks the minimum cache lifetime on a per-table basis:
 
 http://www.lullabot.com/files/memcache-inc.png
 http://www.lullabot.com/files/memcache-db-inc.png
Index: dmemcache.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/memcache/dmemcache.inc,v
retrieving revision 1.1.2.7.2.2
retrieving revision 1.1.2.7.2.4
diff -u -r1.1.2.7.2.2 -r1.1.2.7.2.4
--- dmemcache.inc	1 Nov 2008 12:32:13 -0000	1.1.2.7.2.2
+++ dmemcache.inc	26 Jan 2009 21:12:04 -0000	1.1.2.7.2.4
@@ -1,5 +1,5 @@
 <?php
-// $Id: dmemcache.inc,v 1.1.2.7.2.2 2008/11/01 12:32:13 jvandyk Exp $
+// $Id: dmemcache.inc,v 1.1.2.7.2.4 2009/01/26 21:12:04 jeremy Exp $
 
 /*
  * Core dmemcache functions required by:
@@ -62,7 +62,21 @@
     $full_key = dmemcache_key($key, $bin);
     $result = $mc->get($full_key);
     if ($result) {
-      $_memcache_statistics['hit'][] = $key;
+      // We check $result->expire to see if the object has expired.  If so, we
+      // try and grab a lock.  If we get the lock, we return FALSE instead of
+      // the cached object which should cause it to be rebuilt.  If we do not
+      // get the lock, we return the cached object.  The goal here is to avoid
+      // cache stampedes. 
+      // By default the cache stampede semaphore is held for 15 seconds.  This
+      // can be adjusted by setting the memcache_stampede_semaphore variable.
+      // TODO: Can we log when a sempahore expires versus being intentionally
+      // freed to track when this is happening?
+      if ($result->expire && $result->expire <= time() && $mc->add($full_key .'_semaphore', '', FALSE, variable_get('memcache_stampede_semaphore', 15))) {
+        $result = FALSE;
+      }
+      else {
+        $_memcache_statistics['hit'][] = $key;
+      }
     }
     return $result;
   }
Index: memcache-session.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/memcache/memcache-session.inc,v
retrieving revision 1.1.2.2.2.8
retrieving revision 1.1.2.2.2.10
diff -u -r1.1.2.2.2.8 -r1.1.2.2.2.10
--- memcache-session.inc	26 Jan 2009 15:11:08 -0000	1.1.2.2.2.8
+++ memcache-session.inc	14 Jul 2009 22:07:11 -0000	1.1.2.2.2.10
@@ -1,5 +1,7 @@
 <?php
-// $Id: memcache-session.inc,v 1.1.2.2.2.8 2009/01/26 15:11:08 jvandyk Exp $
+// $Id: memcache-session.inc,v 1.1.2.2.2.10 2009/07/14 22:07:11 jeremy Exp $
+
+require_once dirname(__FILE__) . '/dmemcache.inc';
 
 /**
  * @file
@@ -105,6 +107,8 @@
     // to duplicate it in users memcache.
     unset($user->session);
     unset($user->session_data_present_at_load);
+    // Store the session id so we can locate the session with the user id.
+    $user->sid = $key;
     
     dmemcache_set($user->uid, $user, ini_get('session.gc_maxlifetime'), 'users');
   }
@@ -161,9 +165,14 @@
 }
 
 /**
- * End a specific user's session. Not implemented.
+ * End a specific user's session.
  */
 function sess_destroy_uid($uid) {
+  $user = dmemcache_get($uid, 'users');
+  if (is_object($user) && isset($user->sid)) {
+    dmemcache_delete($user->sid, 'session');
+  }
+  dmemcache_delete($uid, 'users');
 }
 
 function sess_gc($lifetime) {
Index: memcache.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/memcache/memcache.inc,v
retrieving revision 1.15.2.8.2.3
retrieving revision 1.15.2.8.2.6
diff -u -r1.15.2.8.2.3 -r1.15.2.8.2.6
--- memcache.inc	19 Dec 2008 14:05:58 -0000	1.15.2.8.2.3
+++ memcache.inc	13 Jul 2009 21:33:44 -0000	1.15.2.8.2.6
@@ -1,5 +1,5 @@
 <?php
-// $Id: memcache.inc,v 1.15.2.8.2.3 2008/12/19 14:05:58 jeremy Exp $
+// $Id: memcache.inc,v 1.15.2.8.2.6 2009/07/13 21:33:44 jeremy Exp $
 
 require_once 'dmemcache.inc';
 
@@ -18,7 +18,25 @@
  *   'cache_menu', 'cache_page', or 'cache' for the default cache.
  */
 function cache_get($cid, $table = 'cache') {
-  return dmemcache_get($cid, $table);
+  // Determine when the current table was last flushed.
+  $cache_flush = variable_get("cache_flush_$table", 0);
+  // Retrieve the item from the cache.
+  $cache = dmemcache_get($cid, $table);
+  if (is_object($cache)) {
+    $cache_tables = 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_tables) && $cache_tables[$table] &&
+        $cache_tables[$table] > $cache->created)) {
+      // Cache item expired, return NULL.
+      return 0;
+    }
+    return $cache;
+  }
+  return 0;
 }
 
 /**
@@ -57,9 +75,13 @@
 
   // Save to memcache
   if ($expire == CACHE_TEMPORARY) {
-    $expire = variable_get('cache_lifetime', 2591999);
+    $expire = 2591999;
   }
-  dmemcache_set($cid, $cache, $expire, $table);
+  // 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, $table);
 }
 
 /**
@@ -103,7 +125,29 @@
     }
   }
   else if ($cid == '*' || $wildcard === TRUE) {
-    dmemcache_flush($table);
+    if (variable_get('cache_lifetime', 0)) {
+      // Update the timestamp of the last global flushing of this table.  When
+      // retrieving data from this table, 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_$table", 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_tables = $_SESSION['cache_flush'];
+      }
+      else {
+        $cache_tables = array();
+      }
+      $cache_tables[$table] = time();
+      $_SESSION['cache_flush'] = $cache_tables;
+    }
+    else {
+      dmemcache_flush($table);
+    }
   }
   else {
     dmemcache_delete($cid, $table);

