 includes/bootstrap.inc              |   41 +++++++++++----
 includes/common.inc                 |   15 +++++-
 includes/lock.inc                   |    9 +++-
 modules/comment/comment.module      |   35 ++++++++-----
 modules/node/node.module            |   66 +++++++++++++-----------
 modules/taxonomy/taxonomy.admin.inc |    2 +
 modules/taxonomy/taxonomy.module    |   96 ++++++++++++++++++++---------------
 modules/user/user.module            |   39 ++++++++------
 8 files changed, 188 insertions(+), 115 deletions(-)

diff --git includes/bootstrap.inc includes/bootstrap.inc
index 2a80b06..73bfc11 100644
--- includes/bootstrap.inc
+++ includes/bootstrap.inc
@@ -860,11 +860,29 @@ function drupal_page_get_cache($check_only = FALSE) {
   }
 
   if (drupal_page_is_cacheable()) {
-    $cache = cache_get($base_root . request_uri(), 'cache_page');
+    // If the site is configured to load the page from cache without hitting
+    // the database, drupal_page_get_cache() could get called before the lock
+    // system has been initialized.
+    if (variable_get('page_cache_without_database')) {
+      require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc');
+      lock_initialize();
+    }
+
+    $cid = $base_root . request_uri();
+    if (!$cache = cache_get($cid, 'cache_page') && !lock_acquire($cid)) {
+      lock_wait($cid);
+      if (!$cache = cache_get($cid, 'cache_page')) {
+        if (lock_acquire($cid)) {
+          // Proceed with page build, cache its result, then release the lock.
+          header('X-Drupal-Cache: MISS');
+        }
+      }
+    }
+
     if ($cache !== FALSE) {
       $cache_hit = TRUE;
+      return $cache;
     }
-    return $cache;
   }
 }
 
@@ -2074,13 +2092,17 @@ function _drupal_bootstrap_page_cache() {
   foreach (variable_get('cache_backends', array()) as $include) {
     require_once DRUPAL_ROOT . '/' . $include;
   }
-  // Check for a cache mode force from settings.php.
+  // Check for a cache mode force from settings.php. It's not safe to invoke
+  // hooks without the database, so set a flag based on the value of
+  // $conf['page_cache_without_database'].
   if (variable_get('page_cache_without_database')) {
     $cache_enabled = TRUE;
+    $hook_invocation_is_safe = FALSE;
   }
   else {
     drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE);
     $cache_enabled = variable_get('cache');
+    $hook_invocation_is_safe = TRUE;
   }
   drupal_block_denied(ip_address());
   // If there is no session cookie and cache is enabled (or forced), try
@@ -2098,23 +2120,18 @@ function _drupal_bootstrap_page_cache() {
       $_GET['q'] = $cache->data['path'];
       drupal_set_title($cache->data['title'], PASS_THROUGH);
       date_default_timezone_set(drupal_get_user_timezone());
-      // If the skipping of the bootstrap hooks is not enforced, call
-      // hook_boot.
-      if (variable_get('page_cache_invoke_hooks', TRUE)) {
+      // If the skipping of bootstrap hooks is not enforced, call hook_boot().
+      if ($hook_invocation_is_safe && variable_get('page_cache_invoke_hooks', TRUE)) {
         bootstrap_invoke_all('boot');
       }
       drupal_serve_page_from_cache($cache);
-      // If the skipping of the bootstrap hooks is not enforced, call
-      // hook_exit.
-      if (variable_get('page_cache_invoke_hooks', TRUE)) {
+      // If the skipping of bootstrap hooks is not enforced, call hook_exit().
+      if ($hook_invocation_is_safe && variable_get('page_cache_invoke_hooks', TRUE)) {
         bootstrap_invoke_all('exit');
       }
       // We are done.
       exit;
     }
-    else {
-      header('X-Drupal-Cache: MISS');
-    }
   }
 }
 
diff --git includes/common.inc includes/common.inc
index abaf7a9..add54b2 100644
--- includes/common.inc
+++ includes/common.inc
@@ -4890,6 +4890,7 @@ function drupal_page_set_cache() {
         $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
       }
       cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire);
+      lock_release($cache->cid);
     }
     return $cache;
   }
@@ -5577,7 +5578,14 @@ function drupal_render_cache_get($elements) {
   }
   $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
 
-  if (!empty($cid) && $cache = cache_get($cid, $bin)) {
+  if (!$cache = cache_get($cid, $bin) && !lock_acquire($cid)) {
+    lock_wait($cid);
+    if (!$cache = cache_get($cid, $bin)) {
+      lock_acquire($cid);
+    }
+  }
+
+  if ($cache) {
     // Add additional libraries, JavaScript, CSS and other data attached
     // to this element.
     if (isset($cache->data['#attached'])) {
@@ -5586,6 +5594,10 @@ function drupal_render_cache_get($elements) {
     // Return the rendered output.
     return $cache->data['#markup'];
   }
+
+  // Proceed with rendering. Even if we failed to acquire the lock, we
+  // should try to render the elements. Locking is a pluggable system, so the
+  // site may still function even when repeated attempts to use it fail.
   return FALSE;
 }
 
@@ -5622,6 +5634,7 @@ function drupal_render_cache_set(&$markup, $elements) {
   $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);
+  lock_release($cid);
 }
 
 /**
diff --git includes/lock.inc includes/lock.inc
index 546f6bf..34e8ee5 100644
--- includes/lock.inc
+++ includes/lock.inc
@@ -198,11 +198,16 @@ function lock_may_be_available($name) {
  */
 function lock_wait($name, $delay = 30) {
   $delay = (int) $delay;
-  while ($delay--) {
+  $sleep = 1;
+  while ($delay > 0) {
     // This function should only be called by a request that failed to get a
     // lock, so we sleep first to give the parallel request a chance to finish
     // and release the lock.
-    sleep(1);
+    sleep($sleep);
+    // After each sleep, double the value of $sleep, to reduce the potential
+    // for a lock stampede.
+    $delay = $delay - $sleep;
+    $sleep = min($sleep * 2, $delay);
     if (lock_may_be_available($name)) {
       // No longer need to wait.
       return FALSE;
diff --git modules/comment/comment.module modules/comment/comment.module
index d8afe0f..209740d 100644
--- modules/comment/comment.module
+++ modules/comment/comment.module
@@ -1588,20 +1588,27 @@ function comment_delete($cid) {
 function comment_delete_multiple($cids) {
   $comments = comment_load_multiple($cids);
   if ($comments) {
-
-    // Delete the comments.
-    db_delete('comment')
-      ->condition('cid', array_keys($comments), 'IN')
-      ->execute();
-    foreach ($comments as $comment) {
-      field_attach_delete('comment', $comment);
-      module_invoke_all('comment_delete', $comment);
-      module_invoke_all('entity_delete', $comment, 'comment');
-
-      // Delete the comment's replies.
-      $child_cids = db_query('SELECT cid FROM {comment} WHERE pid = :cid', array(':cid' => $comment->cid))->fetchCol();
-      comment_delete_multiple($child_cids);
-      _comment_update_node_statistics($comment->nid);
+    $transaction = db_transaction();
+    try {
+      // Delete the comments.
+      db_delete('comment')
+        ->condition('cid', array_keys($comments), 'IN')
+        ->execute();
+      foreach ($comments as $comment) {
+        field_attach_delete('comment', $comment);
+        module_invoke_all('comment_delete', $comment);
+        module_invoke_all('entity_delete', $comment, 'comment');
+
+        // Delete the comment's replies.
+        $child_cids = db_query('SELECT cid FROM {comment} WHERE pid = :cid', array(':cid' => $comment->cid))->fetchCol();
+        comment_delete_multiple($child_cids);
+        _comment_update_node_statistics($comment->nid);
+      }
+    }
+    catch (Exception $e) {
+      $transaction->rollback();
+      watchdog_exception('comment', $e);
+      throw $e;
     }
   }
 }
diff --git modules/node/node.module modules/node/node.module
index f4e4343..71c3959 100644
--- modules/node/node.module
+++ modules/node/node.module
@@ -1123,7 +1123,7 @@ function node_save($node) {
     db_ignore_slave();
   }
   catch (Exception $e) {
-    $transaction->rollback('node');
+    $transaction->rollback();
     watchdog_exception('node', $e);
     throw $e;
   }
@@ -1164,41 +1164,49 @@ function node_delete($nid) {
  *   An array of node IDs.
  */
 function node_delete_multiple($nids) {
+  $transaction = db_transaction();
   if (!empty($nids)) {
     $nodes = node_load_multiple($nids, array());
 
-    foreach ($nodes as $nid => $node) {
-      // Call the node-specific callback (if any):
-      node_invoke($node, 'delete');
-      module_invoke_all('node_delete', $node);
-      module_invoke_all('entity_delete', $node, 'node');
-      field_attach_delete('node', $node);
-
-      // Remove this node from the search index if needed.
-      // This code is implemented in node module rather than in search module,
-      // because node module is implementing search module's API, not the other
-      // way around.
-      if (module_exists('search')) {
-        search_reindex($nid, 'node');
+    try {
+      foreach ($nodes as $nid => $node) {
+        // Call the node-specific callback (if any):
+        node_invoke($node, 'delete');
+        module_invoke_all('node_delete', $node);
+        module_invoke_all('entity_delete', $node, 'node');
+        field_attach_delete('node', $node);
+
+        // Remove this node from the search index if needed.
+        // This code is implemented in node module rather than in search module,
+        // because node module is implementing search module's API, not the other
+        // way around.
+        if (module_exists('search')) {
+          search_reindex($nid, 'node');
+        }
       }
-    }
 
-    // Delete after calling hooks so that they can query node tables as needed.
-    db_delete('node')
-      ->condition('nid', $nids, 'IN')
-      ->execute();
-    db_delete('node_revision')
-      ->condition('nid', $nids, 'IN')
-      ->execute();
-    db_delete('history')
-      ->condition('nid', $nids, 'IN')
-      ->execute();
-    db_delete('node_access')
-      ->condition('nid', $nids, 'IN')
-      ->execute();
+      // Delete after calling hooks so that they can query node tables as needed.
+      db_delete('node')
+        ->condition('nid', $nids, 'IN')
+        ->execute();
+      db_delete('node_revision')
+        ->condition('nid', $nids, 'IN')
+        ->execute();
+      db_delete('history')
+        ->condition('nid', $nids, 'IN')
+        ->execute();
+      db_delete('node_access')
+       ->condition('nid', $nids, 'IN')
+       ->execute();
+
+    }
+    catch (Exception $e) {
+      $transaction->rollback();
+      watchdog_exception('node', $e);
+      throw $e;
+    }
 
     // Clear the page and block and node_load_multiple caches.
-    cache_clear_all();
     entity_get_controller('node')->resetCache();
   }
 }
diff --git modules/taxonomy/taxonomy.admin.inc modules/taxonomy/taxonomy.admin.inc
index e81eebb..5081e52 100644
--- modules/taxonomy/taxonomy.admin.inc
+++ modules/taxonomy/taxonomy.admin.inc
@@ -902,6 +902,7 @@ function taxonomy_term_confirm_delete_submit($form, &$form_state) {
   drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name'])));
   watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
   $form_state['redirect'] = 'admin/structure/taxonomy';
+  cache_clear_all();
   return;
 }
 
@@ -941,6 +942,7 @@ function taxonomy_vocabulary_confirm_delete_submit($form, &$form_state) {
   drupal_set_message(t('Deleted vocabulary %name.', array('%name' => $form_state['values']['name'])));
   watchdog('taxonomy', 'Deleted vocabulary %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE);
   $form_state['redirect'] = 'admin/structure/taxonomy';
+  cache_clear_all();
   return;
 }
 
diff --git modules/taxonomy/taxonomy.module modules/taxonomy/taxonomy.module
index b300354..e08bcb9 100644
--- modules/taxonomy/taxonomy.module
+++ modules/taxonomy/taxonomy.module
@@ -423,21 +423,29 @@ function taxonomy_vocabulary_delete($vid) {
 
   // Only load terms without a parent, child terms will get deleted too.
   $result = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid = :vid AND th.parent = 0', array(':vid' => $vid))->fetchCol();
-  foreach ($result as $tid) {
-    taxonomy_term_delete($tid);
-  }
-  db_delete('taxonomy_vocabulary')
-    ->condition('vid', $vid)
-    ->execute();
 
-  field_attach_delete_bundle('taxonomy_term', $vocabulary['machine_name']);
-  module_invoke_all('taxonomy_vocabulary_delete', $vocabulary);
-  module_invoke_all('entity_delete', $vocabulary, 'taxonomy_vocabulary');
+  $transaction = db_transaction();
+  try {
+    foreach ($result as $tid) {
+      taxonomy_term_delete($tid);
+    }
+    db_delete('taxonomy_vocabulary')
+      ->condition('vid', $vid)
+      ->execute();
 
-  cache_clear_all();
-  entity_get_controller('taxonomy_vocabulary')->resetCache();
+    field_attach_delete_bundle('taxonomy_term', $vocabulary['machine_name']);
+    module_invoke_all('taxonomy_vocabulary_delete', $vocabulary);
+    module_invoke_all('entity_delete', $vocabulary, 'taxonomy_vocabulary');
+
+    entity_get_controller('taxonomy_vocabulary')->resetCache();
 
-  return SAVED_DELETED;
+    return SAVED_DELETED;
+  }
+  catch (Exception $e) {
+    $transaction->rollback();
+    watchdog_exception('taxonomy', $e);
+    throw $e;
+  }
 }
 
 /**
@@ -588,41 +596,47 @@ function taxonomy_term_save($term) {
  *   Status constant indicating deletion.
  */
 function taxonomy_term_delete($tid) {
-  $tids = array($tid);
-  while ($tids) {
-    $children_tids = $orphans = array();
-    foreach ($tids as $tid) {
-      // See if any of the term's children are about to be become orphans:
-      if ($children = taxonomy_get_children($tid)) {
-        foreach ($children as $child) {
-          // If the term has multiple parents, we don't delete it.
-          $parents = taxonomy_get_parents($child->tid);
-          if (count($parents) == 1) {
-            $orphans[] = $child->tid;
+  $transaction = db_transaction();
+  try {
+    $tids = array($tid);
+    while ($tids) {
+      $children_tids = $orphans = array();
+      foreach ($tids as $tid) {
+        // See if any of the term's children are about to be become orphans:
+        if ($children = taxonomy_get_children($tid)) {
+          foreach ($children as $child) {
+            // If the term has multiple parents, we don't delete it.
+            $parents = taxonomy_get_parents($child->tid);
+            if (count($parents) == 1) {
+              $orphans[] = $child->tid;
+            }
           }
         }
-      }
 
-      if ($term = taxonomy_term_load($tid)) {
-        db_delete('taxonomy_term_data')
-          ->condition('tid', $tid)
-          ->execute();
-        db_delete('taxonomy_term_hierarchy')
-          ->condition('tid', $tid)
-          ->execute();
-
-        field_attach_delete('taxonomy_term', $term);
-        module_invoke_all('taxonomy_term_delete', $term);
-        module_invoke_all('entity_delete', $term, 'taxonomy_term');
-        taxonomy_terms_static_reset();
+        if ($term = taxonomy_term_load($tid)) {
+          db_delete('taxonomy_term_data')
+            ->condition('tid', $tid)
+            ->execute();
+          db_delete('taxonomy_term_hierarchy')
+            ->condition('tid', $tid)
+            ->execute();
+
+          field_attach_delete('taxonomy_term', $term);
+          module_invoke_all('taxonomy_term_delete', $term);
+          module_invoke_all('entity_delete', $term, 'taxonomy_term');
+          taxonomy_terms_static_reset();
+        }
       }
-    }
 
-    $tids = $orphans;
+      $tids = $orphans;
+    }
+    return SAVED_DELETED;
+  }
+  catch (Exception $e) {
+    $transaction->rollback();
+    watchdog_exception('taxonomy', $e);
+    throw $e;
   }
-
-  cache_clear_all();
-  return SAVED_DELETED;
 }
 
 /**
diff --git modules/user/user.module modules/user/user.module
index f263ea8..8c536a6 100644
--- modules/user/user.module
+++ modules/user/user.module
@@ -2372,23 +2372,30 @@ function user_delete_multiple(array $uids) {
   if (!empty($uids)) {
     $accounts = user_load_multiple($uids, array());
 
-    foreach ($accounts as $uid => $account) {
-      module_invoke_all('user_delete', $account);
-      module_invoke_all('entity_delete', $account, 'user');
-      field_attach_delete('user', $account);
-      drupal_session_destroy_uid($account->uid);
-    }
-
-    db_delete('users')
-      ->condition('uid', $uids, 'IN')
-      ->execute();
-    db_delete('users_roles')
-      ->condition('uid', $uids, 'IN')
-      ->execute();
-    db_delete('authmap')
-      ->condition('uid', $uids, 'IN')
-      ->execute();
+    $transaction = db_transaction();
+    try {
+      foreach ($accounts as $uid => $account) {
+        module_invoke_all('user_delete', $account);
+        module_invoke_all('entity_delete', $account, 'user');
+        field_attach_delete('user', $account);
+        drupal_session_destroy_uid($account->uid);
+      }
 
+      db_delete('users')
+        ->condition('uid', $uids, 'IN')
+        ->execute();
+      db_delete('users_roles')
+        ->condition('uid', $uids, 'IN')
+        ->execute();
+      db_delete('authmap')
+        ->condition('uid', $uids, 'IN')
+        ->execute();
+    }
+    catch (Exception $e) {
+      $transaction->rollback();
+      watchdog_exception('user', $e);
+      throw $e;
+    }
     entity_get_controller('user')->resetCache();
   }
 }
