Index: includes/bootstrap.inc =================================================================== --- includes/bootstrap.inc (revision 3087) +++ includes/bootstrap.inc (working copy) @@ -106,6 +106,12 @@ define('DRUPAL_AUTHENTICATED_RID', 2); /** + * The maximum time to allow for a lock to be held when rebuilding a page for + * the page cache -- this is to prevent stale locks from aborted sessions. + */ +define('CACHE_REBUILD_MAX', 5); + +/** * Start the timer with the specified name. If you start and stop * the same timer multiple times, the measured intervals will be * accumulated. @@ -473,12 +479,23 @@ if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0) { $cache = cache_get($base_root . request_uri(), 'cache_page'); + // Check if the currently cached page was created before the cache was + // last flushed. If so, try to rebuild it. + if (is_object($cache) && $cache->timestamp < variable_get('cache_page_clear', 0)) { + $lock = cache_get($base_root . request_uri(), 'cache_lock'); + if (!$lock) { + // Grab the lock and rebuild the web page. + cache_set($base_root . request_uri(), 'cache_lock', time(), time() + CACHE_REBUILD_MAX); + $cache = NULL; + } + } + if (empty($cache)) { ob_start(); } } - return $cache; + return $cache->contents; } /** Index: includes/common.inc =================================================================== --- includes/common.inc (revision 3087) +++ includes/common.inc (working copy) @@ -1894,8 +1894,10 @@ global $user, $base_root; if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_get_messages(NULL, FALSE)) == 0) { + $data = NULL; // This will fail in some cases, see page_get_cache() for the explanation. - if ($data = ob_get_contents()) { + if ($data->contents = ob_get_contents()) { + $data->timestamp = time(); $cache = TRUE; if (function_exists('gzencode')) { // We do not store the data in case the zlib mode is deflate. @@ -1904,14 +1906,16 @@ $cache = FALSE; } else if (zlib_get_coding_type() == FALSE) { - $data = gzencode($data, 9, FORCE_GZIP); + $data->contents = gzencode($data->contents, 9, FORCE_GZIP); } // The remaining case is 'gzip' which means the data is // already compressed and nothing left to do but to store it. } ob_end_flush(); - if ($cache && $data) { + if ($cache && is_object($data) && $data->contents) { cache_set($base_root . request_uri(), 'cache_page', $data, CACHE_TEMPORARY, drupal_get_headers()); + // Clear lock that prevented multiple requests rebuilding the same page. + cache_clear_all($base_root . request_uri(), 'cache_lock'); } } } Index: sites/default/modules/memcache/memcache.inc =================================================================== --- sites/default/modules/memcache/memcache.inc (revision 3087) +++ sites/default/modules/memcache/memcache.inc (working copy) @@ -80,6 +80,13 @@ * match. If '*' is given as $cid, the table $table will be emptied. */ function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) { + // Special handling for the page cache. Rather than flushing this table, we + // update a variable. When pages are in high demand, this allows us to serve + // the old cached version while an updated version is being created. + if ($table == 'cache_page') { + variable_set('cache_page_clear', time()); + return; + } // Memcache logic is simpler because memcache doesn't have a minimum cache // lifetime consideration (it handles it internally), and doesn't support // wildcards.