=== modified file 'includes/bootstrap.inc'
--- includes/bootstrap.inc	2010-07-26 03:05:36 +0000
+++ includes/bootstrap.inc	2010-08-14 22:23:02 +0000
@@ -49,6 +49,14 @@ define('CACHE_PERMANENT', 0);
 define('CACHE_TEMPORARY', -1);
 
 /**
+ * Pass this as $version to cache_set() if cache_get() failed.
+ *
+ * @see cache_get()
+ * @see cache_set()
+ */
+define('CACHE_DOES_NOT_EXIST', -1);
+
+/**
  * Log message severity -- Emergency: system is unusable.
  *
  * The WATCHDOG_* constant definitions correspond to the logging severity levels

=== modified file 'includes/cache.inc'
--- includes/cache.inc	2010-05-18 18:26:30 +0000
+++ includes/cache.inc	2010-08-14 22:25:34 +0000
@@ -35,6 +35,34 @@ function _cache_get_object($bin) {
  * Data may be stored as either plain text or as serialized data. cache_get
  * will automatically return unserialized objects and arrays.
  *
+ * Typically, cache_get() is used in the beginning of a long running
+ * function with a cache_set() at the end. For example:
+ * @code
+ *  if ($cache = cache_get($cid, $bin)) {
+ *    return $cache->data;
+ *  }
+ *  ....
+ *  cache_set($cid, $data, $bin);
+ * @endcode
+ *
+ * However, if an object this function works with is changed while the
+ * function runs then cache_set() will store an outdated version. This can be
+ * avoided by choosing a cache ID that changes along with the objects the
+ * function needs but this is not always feasible. In those cases, the
+ * following can be used:
+ * @code
+ *  $cache = cache_get($cid, $bin, TRUE);
+ *  $cache_version = $cache ? $cache->version : CACHE_DOES_NOT_EXIST;
+ *  if ($cache && $cache->version == $cache->version_valid) {
+ *    return $cache->data;
+ *  }
+ *  ...
+ *  cache_set($cid, $data, $bin, $cache_version);
+ * @endcode
+ * 
+ * in this case cache_set() will not write the cache if the cache in case has
+ * been changed cleared since the cache_get() call.
+ *
  * @param $cid
  *   The cache ID of the data to retrieve.
  * @param $bin
@@ -42,12 +70,13 @@ function _cache_get_object($bin) {
  *   'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
  *   'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for
  *   the default cache.
- *
+ * @param $include_invalid
+ *   Set to TRUE to receive invalid records and version information.
  * @return
  *   The cache or FALSE on failure.
  */
-function cache_get($cid, $bin = 'cache') {
-  return _cache_get_object($bin)->get($cid);
+function cache_get($cid, $bin = 'cache', $include_invalid = FALSE) {
+  return _cache_get_object($bin)->get($cid, $include_invalid);
 }
 
 /**
@@ -125,6 +154,9 @@ function cache_get_multiple(array &$cids
  *   'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
  *   'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default
  *   cache.
+ ( @param $version
+ *   Optional. The cache object version received from cache_get() or
+ *   CACHE_DOES_NOT_EXIST if cache_get() returned FALSE.
  * @param $expire
  *   One of the following values:
  *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
@@ -134,7 +166,7 @@ function cache_get_multiple(array &$cids
  *   - A Unix timestamp: Indicates that the item should be kept at least until
  *     the given time, after which it behaves like CACHE_TEMPORARY.
  */
-function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
+function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT, $version = NULL) {
   return _cache_get_object($bin)->set($cid, $data, $expire);
 }
 
@@ -230,7 +262,7 @@ interface DrupalCacheInterface {
    * @return
    *   The cache or FALSE on failure.
    */
-  function get($cid);
+  function get($cid, $include_invalid = FALSE);
 
   /**
    * Return data from the persistent cache when given an array of cache IDs.
@@ -262,7 +294,7 @@ interface DrupalCacheInterface {
    *   - A Unix timestamp: Indicates that the item should be kept at least until
    *     the given time, after which it behaves like CACHE_TEMPORARY.
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT);
+  function set($cid, $data, $expire = CACHE_PERMANENT, $version = NULL);
 
 
   /**
@@ -304,11 +336,16 @@ class DrupalDatabaseCache implements Dru
     $this->bin = $bin;
   }
 
-  function get($cid) {
+  function get($cid, $include_invalid = FALSE) {
     try {
       // Garbage collection necessary when enforcing a minimum cache lifetime.
       $this->garbageCollection($this->bin);
-      $cache = db_query("SELECT data, created, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
+      if ($include_invalid) {
+        $cache = db_query("SELECT data, created, expire, serialized, version, version_valid FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
+      }
+      else {
+        $cache = db_query("SELECT data, created, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid AND version = version_valid", array(':cid' => $cid))->fetchObject();
+      }
       return $this->prepareItem($cache);
     }
     catch (Exception $e) {
@@ -325,6 +362,7 @@ class DrupalDatabaseCache implements Dru
       $query = db_select($this->bin);
       $query->fields($this->bin, array('cid', 'data', 'created', 'expire', 'serialized'));
       $query->condition($this->bin . '.cid', $cids, 'IN');
+      $query->where($this->bin . '.version = ' . $this->bin . '.version_valid');
       $result = $query->execute();
       $cache = array();
       foreach ($result as $item) {
@@ -401,7 +439,7 @@ class DrupalDatabaseCache implements Dru
     return $cache;
   }
 
-  function set($cid, $data, $expire = CACHE_PERMANENT) {
+  function set($cid, $data, $expire = CACHE_PERMANENT, $version = NULL) {
     $fields = array(
       'serialized' => 0,
       'created' => REQUEST_TIME,
@@ -415,10 +453,24 @@ class DrupalDatabaseCache implements Dru
       $fields['data'] = $data;
       $fields['serialized'] = 0;
     }
-
+    $key = array('cid' => $cid);
+    if (isset($version)) {
+      // If there was already a record, update it but only if the version did
+      // not change. If the record did change, the merge will not find the
+      // record and will try an insert and fail because 'cid' is the primary
+      // key which is what we wanted: if the cache has changed since
+      // cache_get() do not write the database.
+      // If there was no record, $version will be -1 which can never match any
+      // record so the merge will be an insert. If another process has written
+      // the cache since cache_get() failed, then this insert will fail which
+      // is again what's desired.
+      $key['version'] = $version;
+      // If the write succeeds, then the record is valid.
+      $fields['version_valid'] = $version;
+    }
     try {
       db_merge($this->bin)
-        ->key(array('cid' => $cid))
+        ->key($key)
         ->fields($fields)
         ->execute();
     }
@@ -446,28 +498,45 @@ class DrupalDatabaseCache implements Dru
         elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
           // Clear the cache for everyone, cache_lifetime seconds have
           // passed since the first request to clear the cache.
-          db_delete($this->bin)
+          db_update($this->bin)
+            ->fields(array('expire', REQUEST_TIME + 300))
+            ->expression('version', 'version_valid + 1')
             ->condition('expire', CACHE_PERMANENT, '<>')
             ->condition('expire', REQUEST_TIME, '<')
             ->execute();
+          db_delete($this->bin)
+            ->expression('version', 'version_valid + 1', '<>')
+            ->condition('expire', REQUEST_TIME, '<')
+            ->execute();
           variable_set('cache_flush_' . $this->bin, 0);
         }
       }
       else {
         // No minimum cache lifetime, flush all temporary cache entries now.
-        db_delete($this->bin)
+        db_update($this->bin)
+          ->fields(array('expire', REQUEST_TIME + 300))
+          ->expression('version', 'version_valid + 1')
           ->condition('expire', CACHE_PERMANENT, '<>')
           ->condition('expire', REQUEST_TIME, '<')
           ->execute();
+        db_delete($this->bin)
+          ->expression('version', 'version_valid + 1', '<>')
+          ->condition('expire', REQUEST_TIME, '<')
+          ->execute();
       }
     }
     else {
       if ($wildcard) {
         if ($cid == '*') {
-          db_truncate($this->bin)->execute();
+          db_update($this->bin)
+            ->fields(array('expire' => REQUEST_TIME + 300))
+            ->expression('version', 'version_valid + 1')
+            ->execute();
         }
         else {
-          db_delete($this->bin)
+          db_update($this->bin)
+            ->fields(array('expire' => REQUEST_TIME + 300))
+            ->expression('version', 'version_valid + 1')
             ->condition('cid', db_like($cid) . '%', 'LIKE')
             ->execute();
         }
@@ -475,14 +544,18 @@ class DrupalDatabaseCache implements Dru
       elseif (is_array($cid)) {
         // Delete in chunks when a large array is passed.
         do {
-          db_delete($this->bin)
+          db_update($this->bin)
+            ->fields(array('expire' => REQUEST_TIME + 300))
+            ->expression('version', 'version_valid + 1')
             ->condition('cid', array_splice($cid, 0, 1000), 'IN')
             ->execute();
         }
         while (count($cid));
       }
       else {
-        db_delete($this->bin)
+        db_update($this->bin)
+          ->fields(array('expire' => REQUEST_TIME + 300))
+          ->expression('version', 'version_valid + 1')
           ->condition('cid', $cid)
           ->execute();
       }

=== modified file 'includes/common.inc'
--- includes/common.inc	2010-08-14 03:15:01 +0000
+++ includes/common.inc	2010-08-14 22:52:21 +0000
@@ -5255,20 +5255,23 @@ function show(&$element) {
  *   A markup string containing the rendered content of the element, or FALSE
  *   if no cached copy of the element is available.
  */
-function drupal_render_cache_get($elements) {
+function drupal_render_cache_get(&$elements) {
   if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) {
     return FALSE;
   }
   $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
-
-  if (!empty($cid) && $cache = cache_get($cid, $bin)) {
-    // Add additional libraries, JavaScript, CSS and other data attached
-    // to this element.
-    if (isset($cache->data['#attached'])) {
-      drupal_process_attached($cache->data);
+  $cache = cache_get($cid, bin, TRUE);
+  if ($cache) {
+    $elements['#cache']['version'] = $cache->version;
+    if ($cache->version == $cache->version_valid) {
+      // Add additional libraries, JavaScript, CSS and other data attached
+      // to this element.
+      if (isset($cache->data['#attached'])) {
+        drupal_process_attached($cache->data);
+      }
+      // Return the rendered output.
+      return $cache->data['#markup'];
     }
-    // Return the rendered output.
-    return $cache->data['#markup'];
   }
   return FALSE;
 }
@@ -5303,9 +5306,12 @@ function drupal_render_cache_set(&$marku
   if (isset($elements['#attached'])) {
     $data['#attached'] = $elements['#attached'];
   }
-  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
-  $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT;
-  cache_set($cid, $data, $bin, $expire);
+  $cache = $elements['#cache'] + array(
+    'bin' => 'cache',
+    'expire' => CACHE_PERMANENT,
+    'version' => CACHE_DOES_NOT_EXIST,
+  );
+  cache_set($cid, $data, $cache['bin'], $cache['expire'], $cache['version']);
 }
 
 /**

=== modified file 'modules/filter/filter.module'
--- modules/filter/filter.module	2010-08-01 19:50:33 +0000
+++ modules/filter/filter.module	2010-08-14 22:17:04 +0000
@@ -688,9 +688,11 @@ function check_markup($text, $format_id 
   $cache_id = '';
   if ($cache) {
     $cache_id = $format->format . ':' . $langcode . ':' . hash('sha256', $text);
-    if ($cached = cache_get($cache_id, 'cache_filter')) {
+    $cached = cache_get($cache_id, 'cache_filter', TRUE);
+    if ($cached && $cached->version == $cached->version_valid) {
       return $cached->data;
     }
+    $cache_version = $cached ? $cached->version : CACHE_DOES_NOT_EXIST;
   }
 
   // Convert all Windows and Mac newlines to a single newline, so filters only
@@ -719,7 +721,7 @@ function check_markup($text, $format_id 
 
   // Store in cache with a minimum expiration time of 1 day.
   if ($cache) {
-    cache_set($cache_id, $text, 'cache_filter', REQUEST_TIME + (60 * 60 * 24));
+    cache_set($cache_id, $text, 'cache_filter', REQUEST_TIME + (60 * 60 * 24), $cache_version);
   }
 
   return $text;

=== modified file 'modules/system/system.install'
--- modules/system/system.install	2010-07-31 12:29:31 +0000
+++ modules/system/system.install	2010-08-14 19:27:27 +0000
@@ -656,6 +656,18 @@ function system_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'version' => array(
+        'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'version_valid' => array(
+        'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'indexes' => array(
       'expire' => array('expire'),

