Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.255.2.28
diff -u -p -r1.255.2.28 menu.inc
--- includes/menu.inc	9 Feb 2009 16:28:21 -0000	1.255.2.28
+++ includes/menu.inc	10 Feb 2009 03:01:53 -0000
@@ -1654,44 +1654,58 @@ function menu_cache_clear_all() {
  * is different and leaves stale data in the menu tables.
  */
 function menu_rebuild() {
+  if (!lock_acquire('menu_rebuild')) {
+    // Wait for another request to do our work
+    lock_wait('menu_rebuild');
+    return;
+  }
+
   variable_del('menu_rebuild_needed');
   menu_cache_clear_all();
   $menu = menu_router_build(TRUE);
+  lock_renew('menu_rebuild');
   _menu_navigation_links_rebuild($menu);
   // Clear the page and block caches.
   _menu_clear_page_cache();
   if (defined('MAINTENANCE_MODE')) {
     variable_set('menu_rebuild_needed', TRUE);
   }
+  lock_release('menu_rebuild');
 }
 
 /**
  * Collect, alter and store the menu definitions.
  */
-function menu_router_build($reset = FALSE) {
-  static $menu;
+function menu_router_build() {
 
-  if (!isset($menu) || $reset) {
-    if (!$reset && ($cache = cache_get('router:', 'cache_menu')) && isset($cache->data)) {
-      $menu = $cache->data;
-    }
-    else {
-      // We need to manually call each module so that we can know which module
-      // a given item came from.
-      $callbacks = array();
-      foreach (module_implements('menu') as $module) {
-        $router_items = call_user_func($module .'_menu');
-        if (isset($router_items) && is_array($router_items)) {
-          foreach (array_keys($router_items) as $path) {
-            $router_items[$path]['module'] = $module;
-          }
-          $callbacks = array_merge($callbacks, $router_items);
+    // We need to manually call each module so that we can know which module
+    // a given item came from.
+    $callbacks = array();
+    foreach (module_implements('menu') as $module) {
+      $router_items = call_user_func($module .'_menu');
+      if (isset($router_items) && is_array($router_items)) {
+        foreach (array_keys($router_items) as $path) {
+          $router_items[$path]['module'] = $module;
         }
+        $callbacks = array_merge($callbacks, $router_items);
       }
-      // Alter the menu as defined in modules, keys are like user/%user.
-      drupal_alter('menu', $callbacks);
-      $menu = _menu_router_build($callbacks);
     }
+    // Alter the menu as defined in modules, keys are like user/%user.
+    drupal_alter('menu', $callbacks);
+    $menu = _menu_router_build($callbacks);
+    _menu_router_store($menu);
+
+  return $menu;
+}
+
+/**
+ * Helper function to store the menu router if we have it in memory..
+ */
+function _menu_router_store($new_menu = NULL) {
+  static $menu = NULL;
+  
+  if (isset($new_menu)) {
+    $menu = $new_menu;
   }
   return $menu;
 }
@@ -1841,9 +1855,8 @@ function _menu_delete_item($item, $force
  *   saved.
  */
 function menu_link_save(&$item) {
-  $menu = menu_router_build();
 
-  drupal_alter('menu_link', $item, $menu);
+  drupal_alter('menu_link', $item);
 
   // This is the easiest way to handle the unique internal path '<front>',
   // since a path marked as external does not need to match a router path.
@@ -1960,7 +1973,7 @@ function menu_link_save(&$item) {
     else {
       // Find the router path which will serve this path.
       $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
-      $item['router_path'] = _menu_find_router_path($menu, $item['link_path']);
+      $item['router_path'] = _menu_find_router_path($item['link_path']);
     }
   }
   db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, link_path = '%s',
@@ -2021,19 +2034,24 @@ function _menu_set_expanded_menus() {
 /**
  * Find the router path which will serve this path.
  *
- * @param $menu
- *  The full built menu.
  * @param $link_path
  *  The path for we are looking up its router path.
  * @return
  *  A path from $menu keys or empty if $link_path points to a nonexisting
  *  place.
  */
-function _menu_find_router_path($menu, $link_path) {
-  $parts = explode('/', $link_path, MENU_MAX_PARTS);
+function _menu_find_router_path($link_path) {
+  $menu = _menu_router_store();
+
   $router_path = $link_path;
-  if (!isset($menu[$router_path])) {
-    list($ancestors) = menu_get_ancestors($parts);
+  $parts = explode('/', $link_path, MENU_MAX_PARTS);
+  list($ancestors, $placeholders) = menu_get_ancestors($parts);
+
+  if (!isset($menu)) {
+    // Not during a menu rebuild, so look up in the database.
+    $router_path = (string)db_result(db_query_range('SELECT path FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1));
+  }
+  elseif (!isset($menu[$router_path])) {
     $ancestors[] = '';
     foreach ($ancestors as $key => $router_path) {
       if (isset($menu[$router_path])) {
@@ -2391,7 +2409,7 @@ function _menu_router_build($callbacks) 
   $masks = array_keys($masks);
   rsort($masks);
   variable_set('menu_masks', $masks);
-  cache_set('router:', $menu, 'cache_menu');
+
   return $menu;
 }
 
Index: includes/lock.inc
===================================================================
RCS file: includes/lock.inc
diff -N includes/lock.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/lock.inc	10 Feb 2009 03:01:53 -0000
@@ -0,0 +1,118 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Functions to support a databse-mediated locking mechanism.
+ */
+
+/**
+ * Initialize the locking system.
+ */
+function lock_init() {
+  register_shutdown_function('lock_release_all');
+}
+
+function lock_id() {
+  static $lock_id;
+
+  if (!isset($lock_id)) {
+    // Assign this request a unique id
+    $lock_id = uniqid(mt_rand());
+  }
+  return $lock_id;
+}
+
+/**
+ * Acquire a lock, do not block if it fails.
+ *
+ * @param $name
+ *   The name of the lock.
+ * @param $timeout
+ *   A number of seconds (float) before the lock expires.
+ * @return TRUE if the lock was acquired, FALSE if it failed.
+ */
+function lock_acquire($name, $timeout = 10.0) {
+  static $locks = array();
+
+  if (isset($locks[$name])) {
+    return (bool)db_result(db_query("SELECT 1 FROM {semaphore} WHERE name = '%s' AND value = '%s'", $name, lock_id()));
+  }
+  else {
+    // Try to acquire the lock
+    list($usec, $sec) = explode(' ', microtime());
+    $expire = (float)$usec + (float)$sec + $timeout;
+    if (@db_query("INSERT INTO {semaphore} (name, value, expire) VALUES ('%s', '%s', %f)", $name, lock_id(), $expire)) {
+      $locks[$name] = TRUE;
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+}
+
+function lock_renew($name, $timeout = 10.0) {
+  if (lock_acquire($name)) {
+    list($usec, $sec) = explode(' ', microtime());
+    $expire = (float)$usec + (float)$sec + $timeout;
+    db_query("UPDATE {semaphore} SET expire = %f WHERE name = '%s' AND value = '%s'", $expire, $name, lock_id());
+  }
+}
+
+/**
+ * Wait for a lock to be released.
+ *
+ * @param $name
+ *   The name of the lock.
+ * @param $delay
+ *   The maximum number of seconds to wait.
+ * @return TRUE if the lock holds, FALSE if it is available.
+ */
+function lock_wait($name, $delay = 20) {
+
+  while ($delay--) {
+    $expire = db_result(db_query("SELECT expire FROM {semaphore} WHERE name = '%s'", $name));
+    if (!$expire) {
+      return FALSE;
+    }
+    list($usec, $sec) = explode(' ', microtime());
+    $now = (float)$usec + (float)$sec;
+    if ($now > $expire) {
+      local_break($name, $now);
+      return FALSE;
+    }
+    sleep(1);
+  }
+  return TRUE;
+}
+
+/**
+ * Break a lock previously acquired by lock_acquire().
+ *
+ * @param $name
+ *   The name of the lock.
+ * @param $now
+ *   A unix timestamp with microseconds (float).
+ */
+function lock_break($name, $now) {
+  db_query("DELETE FROM {semaphore} WHERE name = '%s' AND expire < %f", $name, $now);
+}
+
+/**
+ * Release a lock previously acquired by lock_acquire().
+ *
+ * @param $name
+ *   The name of the lock.
+ */
+function lock_release($name) {
+  db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s'", $name, lock_id());
+}
+
+/**
+ * Release all previously acquired locks.
+ */
+function lock_release_all() {
+  db_query("DELETE FROM {semaphore} WHERE value = '%s'", lock_id());
+}
+
Index: modules/menu/menu.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v
retrieving revision 1.157.2.3
diff -u -p -r1.157.2.3 menu.module
--- modules/menu/menu.module	10 May 2008 06:53:53 -0000	1.157.2.3
+++ modules/menu/menu.module	10 Feb 2009 03:01:53 -0000
@@ -254,8 +254,7 @@ function _menu_parents_recurse($tree, $m
  * Reset a system-defined menu item.
  */
 function menu_reset_item($item) {
-  $router = menu_router_build();
-  $new_item = _menu_link_build($router[$item['router_path']]);
+  $new_item = _menu_link_build(menu_get_item($item['router_path']));
   foreach (array('mlid', 'has_children') as $key) {
     $new_item[$key] = $item[$key];
   }
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.238.2.8
diff -u -p -r1.238.2.8 system.install
--- modules/system/system.install	14 Jan 2009 21:36:16 -0000	1.238.2.8
+++ modules/system/system.install	10 Feb 2009 03:01:53 -0000
@@ -956,6 +956,31 @@ function system_schema() {
     'primary key' => array('mlid'),
     );
 
+  $schema['semaphore'] = array(
+    'description' => 'Table for storing locks and other flags that should not be cached by the variable system.',
+    'fields' => array(
+      'name' => array(
+        'description' => 'Primary Key: Unique name.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'value' => array(
+        'description' => 'A value.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'expire' => array(
+        'description' => 'A Unix timestamp with microseconds indicating when the semaphore should expire.',
+        'type' => 'float',
+        'size' => 'big',
+        'not null' => TRUE),
+      ),
+    'indexes' => array('expire' => array('expire')),
+    'primary key' => array('name'),
+    );
+
   $schema['sessions'] = array(
     'description' => "Drupal's session handlers read and write into the sessions table. Each record represents a user session, either anonymous or authenticated.",
     'fields' => array(
@@ -2532,6 +2557,38 @@ function system_update_6048() {
   return $ret;
 }
 
+/**
+ * Clear any router blobs stored in the cache table, add semaphore table.
+ */
+function system_update_6049() {
+  $ret = array();
+
+  cache_clear_all('router:', 'cache_menu', TRUE);
+
+  $schema['semaphore'] = array(
+    'fields' => array(
+      'name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'value' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'expire' => array(
+        'type' => 'float',
+        'size' => 'big',
+        'not null' => TRUE),
+      ),
+    'indexes' => array('expire' => array('expire')),
+    'primary key' => array('name'),
+  );
+  db_create_table($ret, 'semaphore', $schema['semaphore']);
+
+  return $ret;
+}
 
 /**
  * @} End of "defgroup updates-5.x-to-6.x"
