=== modified file 'includes/bootstrap.inc'
--- includes/bootstrap.inc	2009-05-09 18:35:00 +0000
+++ includes/bootstrap.inc	2009-05-10 14:27:16 +0000
@@ -28,13 +28,6 @@ define('CACHE_DISABLED', 0);
 define('CACHE_NORMAL', 1);
 
 /**
- * Indicates that page caching is using "aggressive" mode. This bypasses
- * loading any modules for additional speed, which may break functionality in
- * modules that expect to be run on each page load.
- */
-define('CACHE_AGGRESSIVE', 2);
-
-/**
  * Log message severity -- Emergency: system is unusable.
  *
  * @see watchdog()
@@ -104,10 +97,9 @@ define('WATCHDOG_DEBUG', 7);
 define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0);
 
 /**
- * Second bootstrap phase: try to call a non-database cache
- * fetch routine.
+ * Second bootstrap phase: serve the page from cache if possible.
  */
-define('DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE', 1);
+define('DRUPAL_BOOTSTRAP_PAGE_CACHE', 1);
 
 /**
  * Third bootstrap phase: initialize database layer.
@@ -120,20 +112,19 @@ define('DRUPAL_BOOTSTRAP_DATABASE', 2);
 define('DRUPAL_BOOTSTRAP_ACCESS', 3);
 
 /**
- * Fifth bootstrap phase: initialize session handling.
+ * Fifth bootstrap phase: initialize the variable system.
  */
-define('DRUPAL_BOOTSTRAP_SESSION', 4);
+define('DRUPAL_BOOTSTRAP_VARIABLES', 4);
 
 /**
- * Sixth bootstrap phase: initialize the variable system.
+ * Sixth bootstrap phase: initialize session handling.
  */
-define('DRUPAL_BOOTSTRAP_VARIABLES', 5);
+define('DRUPAL_BOOTSTRAP_SESSION', 5);
 
 /**
- * Seventh bootstrap phase: load bootstrap.inc and module.inc, start
- * the variable system and try to serve a page from the cache.
+ * Seventh bootstrap phase: set up the page header.
  */
-define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 6);
+define('DRUPAL_BOOTSTRAP_PAGE_HEADER', 6);
 
 /**
  * Eighth bootstrap phase: find out language of the page.
@@ -391,6 +382,20 @@ function conf_path($require_settings = T
 }
 
 /**
+ * Return the base URL path (i.e., directory) of the Drupal installation.
+ *
+ * base_path() prefixes and suffixes a "/" onto the returned path if the path is 
+ * not empty. At the very least, this will return "/".
+ *
+ * Examples:
+ * - http://example.com returns "/" because the path is empty.
+ * - http://example.com/drupal/folder returns "/drupal/folder/".
+ */
+function base_path() {
+  return $GLOBALS['base_path'];
+}
+
+/**
  * Initialize variables needed for the rest of the execution.
  */
 function drupal_initialize_variables() {
@@ -675,41 +680,71 @@ function variable_del($name) {
 /**
  * Retrieve the current page from the cache.
  *
- * Note: we do not serve cached pages to authenticated users, or to anonymous
+ * Note: We do not serve cached pages to authenticated users, or to anonymous
  * users when $_SESSION is non-empty. $_SESSION may contain status messages
- * from a form submission, the contents of a shopping cart, or other user-
+ * from a form submission, the content of a shopping cart, or other user-
  * specific content that should not be cached and displayed to other users.
  *
- * @param $retrieve
- *   If TRUE, look up and return the current page in the cache, or start output
- *   buffering if the conditions for caching are satisfied. If FALSE, only
- *   return a boolean value indicating whether the current request may be
- *   cached.
+ * @param $caching_mode
+ *   If set, then one of CACHE_DISABLED or CACHE_NORMAL. If NULL, then it will
+ *   be looked up in the database.
  * @return
- *   The cache object, if the page was found in the cache; TRUE if the page was
- *   not found, but output buffering was started in order to possibly cache the
- *   current request; FALSE if the page was not found, and the current request
- *   may not be cached (e.g. because it belongs to an authenticated user). If
- *   $retrieve is TRUE, only return either TRUE or FALSE.
+ *   The cache object, if the page was found in the cache; FALSE if the page
+ *   was not found.
  */
-function page_get_cache($retrieve) {
-  global $user, $base_root;
-  static $ob_started = FALSE;
+function page_get_cache($caching_mode = NULL) {
+  global $base_root;
 
-  if ($user->uid || ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') || count(drupal_get_messages(NULL, FALSE)) || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI') {
-    return FALSE;
-  }
-  if ($retrieve) {
-    $cache = cache_get($base_root . request_uri(), 'cache_page');
-    if ($cache) {
-      return $cache;
-    }
-    else {
+  $cache = FALSE;
+  if (_page_cache_can_be_set($caching_mode)) {
+    // Although this cookie is easy to be forged or deleted, the only
+    // consequence of forgery is that the attacker receives an uncached page.
+    if (!isset($_COOKIE['drupal_nocache'])) {
+      $cache = cache_get($base_root . request_uri(), 'cache_page');
+    }
+    // Regardless of the cookie, start output buffering to allow destroying
+    // the session at the end of the request if it only contained messages.
+    if (!$cache) {
       ob_start();
-      $ob_started = TRUE;
     }
   }
-  return $ob_started;
+  return $cache;
+}
+
+/**
+ * Check whether page caching is allowed for this request.
+ *
+ * Note that if this function returns TRUE then output buffering has been
+ * started in page_get_cache().
+ *
+ * @param $caching_mode
+ *   If set, then one of CACHE_DISABLED or CACHE_NORMAL. If NULL, then it will
+ *   be looked up in the database.
+ * @return
+ *   Boolean, TRUE if the current request may be cached.
+ */
+function _page_cache_can_be_set($caching_mode = NULL) {
+  global $user;
+
+  if (!isset($caching_mode)) {
+    $caching_mode = variable_get('cache', CACHE_DISABLED);
+  }
+  return $caching_mode == CACHE_NORMAL && empty($user->uid) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && $_SERVER['SERVER_SOFTWARE'] !== 'PHP CLI';
+}
+
+/**
+ * Set a cookie to disallow page caching.
+ *
+ * @param $set
+ *   Whether to set the cookie. Internal use only.
+ */
+function page_cache_set_disable_cookie($set = TRUE) {
+  static $cookie_sent = FALSE;
+  if ($set) {
+    $cookie_sent = TRUE;
+    setcookie('drupal_nocache', 1, time() + 20 * 365 * 86400, base_path(), ini_get('session.cookie_domain'));
+  }
+  return $cookie_sent;
 }
 
 /**
@@ -800,7 +835,7 @@ function drupal_set_header($name = NULL,
  *   or NULL if the header has not been set.
  */
 function drupal_get_header($name = NULL) {
-  $headers = drupal_set_header(); 
+  $headers = drupal_set_header();
   if (isset($name)) {
     $name = strtolower($name);
     return isset($headers[$name]) ? $headers[$name] : NULL;
@@ -914,7 +949,7 @@ function drupal_page_header() {
  * and the conditions match those currently in the cache, a 304 Not Modified
  * response is sent.
  */
-function drupal_page_cache_header(stdClass $cache) {
+function drupal_serve_cached_page(stdClass $cache) {
   // Negotiate whether to use compression.
   $page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib');
   $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE;
@@ -937,14 +972,11 @@ function drupal_page_cache_header(stdCla
     }
   }
 
-  // If a cache is served from a HTTP proxy without hitting the web server,
-  // the boot and exit hooks cannot be fired, so only allow caching in
-  // proxies with aggressive caching. If the client send a session cookie, do
-  // not bother caching the page in a public proxy, because the cached copy
-  // will only be served to that particular user due to Vary: Cookie, unless
-  // the Vary header has been replaced or unset in hook_boot() (see below).
-  $max_age = variable_get('cache') == CACHE_AGGRESSIVE && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('cache_lifetime', 0) : 0;
-  $default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
+  // If the client send a session cookie, do not bother caching the page in a
+  // public proxy, because the cached copy will only be served to that
+  // particular user due to Vary: Cookie, unless the Vary header has been
+  // replaced or unset in hook_boot() (see below).
+  $default_headers['Cache-Control'] = 'public, max-age=' . variable_get('cache_lifetime', 0);
 
   // Entity tag should change if the output changes.
   $etag = '"' . $cache->created . '-' . intval($return_compressed) . '"';
@@ -1177,6 +1209,7 @@ function drupal_set_message($message = N
 
     if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
       $_SESSION['messages'][$type][] = $message;
+      page_cache_set_disable_cookie();
     }
   }
 
@@ -1235,11 +1268,26 @@ function drupal_is_denied($ip) {
   // for an array of IP addresses in settings.php before querying the
   // database.
   $blocked_ips = variable_get('blocked_ips');
+  $denied = FALSE;
   if (isset($blocked_ips) && is_array($blocked_ips)) {
-    return in_array($ip, $blocked_ips);
+    $denied = in_array($ip, $blocked_ips);
   }
-  else {
-    return (bool)db_query("SELECT 1 FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField();
+  // Only check if database.inc is loaded already.
+  elseif (function_exists('db_is_active')) {
+    $denied = (bool)db_query("SELECT 1 FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField();
+  }
+  return $denied;
+}
+
+/**
+ * Handle denied users.
+ */
+function drupal_handle_denied($ip) {
+  // Deny access to blocked IP addresses - t() is not yet available.
+  if (drupal_is_denied($ip)) {
+    header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
+    print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.';
+    exit();
   }
 }
 
@@ -1283,12 +1331,12 @@ function drupal_anonymous_user($session 
 function drupal_bootstrap($phase = NULL) {
   $phases = &drupal_static(__FUNCTION__ . '_phases', array(
     DRUPAL_BOOTSTRAP_CONFIGURATION,
-    DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE,
+    DRUPAL_BOOTSTRAP_PAGE_CACHE,
     DRUPAL_BOOTSTRAP_DATABASE,
     DRUPAL_BOOTSTRAP_ACCESS,
-    DRUPAL_BOOTSTRAP_SESSION,
     DRUPAL_BOOTSTRAP_VARIABLES,
-    DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE,
+    DRUPAL_BOOTSTRAP_SESSION,
+    DRUPAL_BOOTSTRAP_PAGE_HEADER,
     DRUPAL_BOOTSTRAP_LANGUAGE,
     DRUPAL_BOOTSTRAP_PATH,
     DRUPAL_BOOTSTRAP_FULL,
@@ -1299,7 +1347,10 @@ function drupal_bootstrap($phase = NULL)
     while ($phases && $phase > $completed_phase) {
       $current_phase = array_shift($phases);
       _drupal_bootstrap($current_phase);
-      $completed_phase = $current_phase;
+      // This function is reentrant. Only update the completed phase when the current call actually resulted in a progress in the bootstrap process.
+      if ($current_phase > $completed_phase) {
+        $completed_phase = $current_phase;
+      }
     }
   }
   return $completed_phase;
@@ -1329,15 +1380,35 @@ function _drupal_bootstrap($phase) {
       conf_init();
       break;
 
-    case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE:
+    case DRUPAL_BOOTSTRAP_PAGE_CACHE:
       // Allow specifying special cache handlers in settings.php, like
       // using memcached or files for storing cache information.
       require_once DRUPAL_ROOT . '/' . variable_get('cache_inc', 'includes/cache.inc');
-      // If the page_cache_fastpath is set to TRUE in settings.php and
-      // page_cache_fastpath (implemented in the special implementation of
-      // cache.inc) printed the page and indicated this with a returned TRUE
-      // then we are done.
-      if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) {
+      // Here we only check whether the cache mode is set in settings.php,
+      // whether caching is on or not is checked in _page_cache_can_be_set()
+      // called from page_set_cache().
+      $cache_mode = variable_get('cache');
+      if (isset($cache_mode)) {
+        // We have a cache mode override from settings.php, we check for denied
+        // addresses but only from settings.php.
+        drupal_handle_denied(ip_address());
+      }
+      else {
+        // We need to initialize the variable system. This will also load the
+        // database and check denied addresses.
+        drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
+      }
+      $cache = page_get_cache($cache_mode);
+      if (is_object($cache)) {
+        if (variable_get('page_cache_invoke_hooks', TRUE)) {
+          require_once DRUPAL_ROOT . '/includes/module.inc';
+          module_invoke_all('boot');
+        }
+        header('X-Drupal-Cache: HIT');
+        drupal_serve_cached_page($cache);
+        if (variable_get('page_cache_invoke_hooks', TRUE)) {
+          module_invoke_all('exit');
+        }
         exit;
       }
       break;
@@ -1352,12 +1423,13 @@ function _drupal_bootstrap($phase) {
       break;
 
     case DRUPAL_BOOTSTRAP_ACCESS:
-      // Deny access to blocked IP addresses - t() is not yet available.
-      if (drupal_is_denied(ip_address())) {
-        header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
-        print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.';
-        exit();
-      }
+      // Check denied addresses, using the database if necessary.
+      drupal_handle_denied(ip_address());
+      break;
+
+    case DRUPAL_BOOTSTRAP_VARIABLES:
+      // Initialize configuration variables, using values from settings.php if available.
+      $conf = variable_init(isset($conf) ? $conf : array());
       break;
 
     case DRUPAL_BOOTSTRAP_SESSION:
@@ -1380,47 +1452,27 @@ function _drupal_bootstrap($phase) {
       $conf = variable_init(isset($conf) ? $conf : array());
       break;
 
-    case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
-      $cache_mode = variable_get('cache', CACHE_DISABLED);
-      // Get the page from the cache.
-      $cache = $cache_mode == CACHE_DISABLED ? FALSE : page_get_cache(TRUE);
-      // If the skipping of the bootstrap hooks is not enforced, call hook_boot.
-      if (!is_object($cache) || $cache_mode != CACHE_AGGRESSIVE) {
-        // Load module handling.
-        require_once DRUPAL_ROOT . '/includes/module.inc';
-        module_invoke_all('boot');
-      }
-      // If there is a cached page, display it.
-      if (is_object($cache)) {
-        // Destroy empty anonymous sessions.
-        if (drupal_session_is_started() && empty($_SESSION)) {
-          session_destroy();
-        }
-        header('X-Drupal-Cache: HIT');
-        drupal_page_cache_header($cache);
-        // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
-        if ($cache_mode != CACHE_AGGRESSIVE) {
-          module_invoke_all('exit');
-        }
-        // We are done.
-        exit;
-      }
-
+    case DRUPAL_BOOTSTRAP_PAGE_HEADER:
       // Prepare for non-cached page workflow.
-
+      require_once DRUPAL_ROOT . '/includes/module.inc';
+      // If the user is anonymous but has the nocache cookie then it was only
+      // valid for one request, so delete it by setting it back to a past time.
+      if (!$user->uid && isset($_COOKIE['drupal_nocache'])) {
+        setcookie('drupal_nocache', 1, 280299600, base_path(), ini_get('session.cookie_domain'));
+      }
       // If the session has not already been started and output buffering is
-      // not enabled, the HTTP headers must be sent now, including the session
-      // cookie. If output buffering is enabled, the session may be started
+      // not enabled, the session must be started now before the HTTP headers
+      // are sent. If output buffering is enabled, the session may be started
       // at any time using drupal_session_start().
-      if ($cache === FALSE) {
-        drupal_page_header();
+      if (!_page_cache_can_be_set()) {
         drupal_session_start();
       }
-      else {
-        header('X-Drupal-Cache: MISS');
-      }
+      header('X-Drupal-Cache: MISS');
+      drupal_page_header();
+      module_invoke_all('boot');
       break;
 
+      // Prepare for non-cached page workflow.
     case DRUPAL_BOOTSTRAP_LANGUAGE:
       drupal_init_language();
       break;
@@ -1822,7 +1874,7 @@ function registry_rebuild() {
  * @param $name
  *   Globally unique name for the variable. For a function with only one static,
  *   variable, the function name (e.g. via the PHP magic __FUNCTION__ constant)
- *   is recommended. For a function with multiple static variables add a 
+ *   is recommended. For a function with multiple static variables add a
  *   distinguishing suffix to the function name for each one.
  * @param $default_value
  *   Optional default value.

=== modified file 'includes/common.inc'
--- includes/common.inc	2009-05-09 22:16:37 +0000
+++ includes/common.inc	2009-05-10 14:25:07 +0000
@@ -1874,9 +1874,7 @@ function drupal_page_footer() {
     watchdog('session', '$_SESSION is non-empty yet no code has called drupal_session_start().', array(), WATCHDOG_NOTICE);
   }
 
-  if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
-    page_set_cache();
-  }
+  page_set_cache();
 
   module_invoke_all('exit');
 
@@ -1933,20 +1931,6 @@ function drupal_get_path($type, $name) {
 }
 
 /**
- * Return the base URL path (i.e., directory) of the Drupal installation.
- *
- * base_path() prefixes and suffixes a "/" onto the returned path if the path is 
- * not empty. At the very least, this will return "/".
- *
- * Examples:
- * - http://example.com returns "/" because the path is empty.
- * - http://example.com/drupal/folder returns "/drupal/folder/".
- */
-function base_path() {
-  return $GLOBALS['base_path'];
-}
-
-/**
  * Add a <link> tag to the page's HEAD.
  *
  * This function can be called as long the HTML header hasn't been sent.
@@ -2998,7 +2982,7 @@ function _drupal_bootstrap_full() {
 function page_set_cache() {
   global $user, $base_root;
 
-  if (page_get_cache(FALSE)) {
+  if (_page_cache_can_be_set() && !isset($_COOKIE['drupal_nocache']) && !page_cache_set_disable_cookie(FALSE)) {
     $cache_page = TRUE;
     $cache = (object) array(
       'cid' => $base_root . request_uri(),
@@ -3028,7 +3012,7 @@ function page_set_cache() {
     if ($cache_page && $cache->data) {
       cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire, $cache->headers);
     }
-    drupal_page_cache_header($cache);
+    drupal_serve_cached_page($cache);
   }
   else {
     // If output buffering was enabled during bootstrap, and the headers were

=== modified file 'modules/simpletest/tests/bootstrap.test'
--- modules/simpletest/tests/bootstrap.test	2009-04-22 09:45:02 +0000
+++ modules/simpletest/tests/bootstrap.test	2009-05-10 03:42:24 +0000
@@ -261,16 +261,16 @@ class HookBootExitTestCase extends Drupa
     $this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'system_test' AND message = 'hook_boot'")->fetchField(), $calls, t('hook_boot called with normal cache.'));
     $this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'system_test' AND message = 'hook_exit'")->fetchField(), $calls, t('hook_exit called with normal cache.'));
 
-    // Test with aggressive cache. Boot and exit should not fire since the
-    // page is cached.
-    variable_set('cache', CACHE_AGGRESSIVE);
-    $this->assertTrue(cache_get(url('', array('absolute' => TRUE)), 'cache_page'), t('Page has been cached.'));
+    // Test whether disabling boot and exit works.
+    variable_set('page_cache_fire_hooks', FALSE);
+    $cache = cache_get(url('', array('absolute' => TRUE)), 'cache_page');
+    $this->assertTrue(is_object($cache), t('Page has been cached.'));
     $this->drupalGet('');
     $this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'system_test' AND message = 'hook_boot'")->fetchField(), $calls, t('hook_boot not called with agressive cache and a cached page.'));
     $this->assertEqual(db_query("SELECT COUNT(*) FROM {watchdog} WHERE type = 'system_test' AND message = 'hook_exit'")->fetchField(), $calls, t('hook_exit not called with agressive cache and a cached page.'));
 
-    // Test with aggressive cache and page cache cleared. Boot and exit should
-    // be called.
+    // Test whether disabling boot and exit only is in effect if page is
+    // cached.
     $this->assertTrue(db_delete('cache_page')->execute(), t('Page cache cleared.'));
     $this->drupalGet('');
     $calls++;

=== modified file 'modules/simpletest/tests/form.test'
--- modules/simpletest/tests/form.test	2009-05-06 19:56:21 +0000
+++ modules/simpletest/tests/form.test	2009-05-10 14:15:43 +0000
@@ -411,7 +411,7 @@ class FormsFormStorageTestCase extends D
    * Tests using the form in a usual way.
    */
   function testForm() {
-    
+
     $user = $this->drupalCreateUser(array('access content'));
     $this->drupalLogin($user);
 
@@ -432,7 +432,7 @@ class FormsFormStorageTestCase extends D
 
     $this->drupalPost('form_test/form-storage', array('title' => 'new', 'value' => 'value_is_set'), 'Continue', array('query' => 'cache=1'));
     $this->assertText('Form constructions: 1', t('The form has been constructed one time till now.'));
-    
+
     $this->drupalPost(NULL, array(), 'Save', array('query' => 'cache=1'));
     $this->assertText('Form constructions: 2', t('The form has been constructed two times till now.'));
     $this->assertText('Title: new', t('The form storage has stored the values.'));

=== modified file 'modules/simpletest/tests/session.test'
--- modules/simpletest/tests/session.test	2009-04-22 09:45:02 +0000
+++ modules/simpletest/tests/session.test	2009-05-10 03:42:24 +0000
@@ -157,8 +157,8 @@ class SessionTestCase extends DrupalWebT
 
     variable_set('cache', CACHE_NORMAL);
 
-    // During this request the session is destroyed in drupal_page_footer(),
-    // and the session cookie is unset.
+    // During this request the session is destroyed and the session cookie is
+    // unset.
     $this->drupalGet('');
     $this->assertSessionCookie(TRUE);
     $this->assertSessionStarted(TRUE);
@@ -168,41 +168,39 @@ class SessionTestCase extends DrupalWebT
     // expires=..."
     $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.'));
 
-    // Verify that the session cookie was actually deleted.
+    // Verify that page caching works.
     $this->drupalGet('');
-    $this->assertSessionCookie(FALSE);
-    $this->assertSessionStarted(FALSE);
+    $this->assertTrue($this->drupalGetHeader('ETag'), t('Page cached.'));
     $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.'));
 
     // Start a new session by setting a message.
     $this->drupalGet('session-test/set-message');
+    $this->assertText(t('A message was set.'), t('A message was set.'));
     $this->assertSessionCookie(FALSE);
     $this->assertSessionStarted(FALSE);
     $this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.'));
 
-    // Display the message.
+    // Display the message. Session cookie is destroyed at the end of the request.
     $this->drupalGet('');
     $this->assertSessionCookie(TRUE);
     $this->assertSessionStarted(TRUE);
     $this->assertSessionEmpty(FALSE);
-    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.'));
+    $this->assertFalse($this->drupalGetHeader('ETag'), t('Page was not cached.'));
     $this->assertText(t('This is a dummy message.'), t('Message was displayed.'));
+    $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.'));
 
-    // During this request the session is destroyed in _drupal_bootstrap(),
-    // and the session cookie is unset.
+    // The next request is cached. Cached requests skip the session system.
     $this->drupalGet('');
-    $this->assertSessionCookie(TRUE);
-    $this->assertSessionStarted(TRUE);
-    $this->assertSessionEmpty(TRUE);
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
+    $this->assertSessionCookie(FALSE);
+    $this->assertSessionStarted(FALSE);
+    $this->assertTrue($this->drupalGetHeader('ETag'), t('Page cached.'));
     $this->assertNoText(t('This is a dummy message.'), t('Message was not cached.'));
-    $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.'));
 
-    // Verify that session was destroyed.
-    $this->drupalGet('');
+    // Next, check an uncached page for no session start.
+    $this->drupalGet($this->randomName());
     $this->assertSessionCookie(FALSE);
     $this->assertSessionStarted(FALSE);
-    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
+    $this->assertFalse($this->drupalGetHeader('ETag'), t('Page was not cached.'));
     $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.'));
 
     // Verify that modifying $_SESSION without having started a session

=== modified file 'modules/simpletest/tests/session_test.module'
--- modules/simpletest/tests/session_test.module	2009-01-26 14:08:40 +0000
+++ modules/simpletest/tests/session_test.module	2009-05-10 03:42:24 +0000
@@ -52,7 +52,8 @@ function session_test_menu() {
  */
 function session_test_boot() {
   header('X-Session-Cookie: ' . intval(isset($_COOKIE[session_name()])));
-  header('X-Session-Started: ' . intval(drupal_session_is_started()));
+  // When called from hook_boot() for cached pages, the session system is not loaded.
+  header('X-Session-Started: ' . intval(function_exists('drupal_session_is_started') && drupal_session_is_started()));
   header('X-Session-Empty: ' . intval(empty($_SESSION)));
 }
 

=== modified file 'modules/system/system.admin.inc'
--- modules/system/system.admin.inc	2009-05-03 07:35:37 +0000
+++ modules/system/system.admin.inc	2009-05-10 14:15:43 +0000
@@ -1287,17 +1287,6 @@ function system_logging_settings() {
  */
 function system_performance_settings() {
 
-  $description = '<p>' . t("The normal page cache mode is suitable for most sites and does not cause any side effects. The aggressive page cache mode causes Drupal to skip the loading (boot) and unloading (exit) of enabled modules when serving a cached page. This results in an additional performance boost but can cause unwanted side effects.") . '</p>';
-
-  $problem_modules = array_unique(array_merge(module_implements('boot'), module_implements('exit')));
-  sort($problem_modules);
-
-  if (count($problem_modules) > 0) {
-    $description .= '<p>' . t('<strong class="error">The following enabled modules are incompatible with aggressive page caching mode and will not function properly: %modules</strong>', array('%modules' => implode(', ', $problem_modules))) . '.</p>';
-  }
-  else {
-    $description .= '<p>' . t('<strong class="ok">Currently, all enabled modules are compatible with the aggressive page caching policy.</strong> Please note, if you use aggressive page caching and enable new modules, you will need to check this setting again to ensure compatibility.') . '</p>';
-  }
   $form['page_cache'] = array(
     '#type' => 'fieldset',
     '#title' => t('Page cache'),
@@ -1309,8 +1298,7 @@ function system_performance_settings() {
     '#type' => 'radios',
     '#title' => t('Page caching mode'),
     '#default_value' => variable_get('cache', CACHE_DISABLED),
-    '#options' => array(CACHE_DISABLED => t('Disabled'), CACHE_NORMAL => t('Normal (recommended for production sites, no side effects)'), CACHE_AGGRESSIVE => t('Aggressive (experts only, possible side effects)')),
-    '#description' => $description
+    '#options' => array(CACHE_DISABLED => t('Disabled'), CACHE_NORMAL => t('Enabled (recommended for production sites)')),
   );
 
   $period = drupal_map_assoc(array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400), 'format_interval');

=== modified file 'modules/system/system.install'
--- modules/system/system.install	2009-05-07 15:29:07 +0000
+++ modules/system/system.install	2009-05-10 14:28:30 +0000
@@ -3439,6 +3439,21 @@ function system_update_7023() {
 }
 
 /**
+ * CACHE_AGGRESSIVE is gone, change it to normal caching and add a warning to
+ * read the documentation.
+ */
+function system_update_7022() {
+  $ret = array();
+  if (variable_get('cache') == 2) {
+    variable_set('cache', CACHE_NORMAL);
+    $t = get_t();
+    $ret[] = array('success' => TRUE, 'query' => $t('This site previously had aggressive caching enabled, to keep this setting, add a variable override for page_cache_fire_hooks in settings.php'));
+  }
+  db_truncate('sessions');
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */

=== modified file 'modules/user/user.module'
--- modules/user/user.module	2009-04-30 21:44:18 +0000
+++ modules/user/user.module	2009-05-10 14:15:43 +0000
@@ -1707,6 +1707,7 @@ function user_authenticate_finalize(&$ed
   // This is called before hook_user in case one of those functions fails
   // or incorrectly does a redirect which would leave the old session in place.
   drupal_session_regenerate();
+  page_cache_set_disable_cookie();
   user_module_invoke('login', $edit, $user);
 }
 

=== modified file 'sites/default/default.settings.php'
--- sites/default/default.settings.php	2009-04-24 08:15:50 +0000
+++ sites/default/default.settings.php	2009-05-10 03:42:24 +0000
@@ -309,6 +309,19 @@ $conf = array(
 # );
 
 /**
+ * Page caching behaviour.
+ *
+ * To use page caching without touching the database set cache to
+ * CACHE_NORMAL and change cache_inc to a non-database cache implementation.
+ * To bypass the hook_boot() and hook_exit() implementations, set the
+ * page_cache_invoke_hooks variable to FALSE. Also see the IP blocking
+ * section below.
+ */
+# $conf['cache'] = CACHE_NORMAL;
+# $conf['cache_inc'] = 'sites/all/modules/memcache/memcache.inc';
+# $conf['page_cache_invoke_hooks'] = FALSE;
+
+/**
  *
  * IP blocking:
  *
@@ -330,3 +343,4 @@ $conf = array(
 # $conf['blocked_ips'] = array(
 #   'a.b.c.d',
 # );
+

