Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.206.2.9 diff -u -p -r1.206.2.9 bootstrap.inc --- includes/bootstrap.inc 14 Jan 2009 19:10:25 -0000 1.206.2.9 +++ includes/bootstrap.inc 11 Feb 2009 00:22:04 -0000 @@ -1042,6 +1042,8 @@ function _drupal_bootstrap($phase) { exit; } // Prepare for non-cached page workflow. + require_once variable_get('lock_inc', './includes/lock.inc'); + lock_init(); drupal_page_header(); break; 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 11 Feb 2009 00:22:04 -0000 @@ -0,0 +1,138 @@ + $expire) { + lock_break($name, $now); + return FALSE; + } + sleep(1); + } + return TRUE; +} + +/** + * Break a lock acquired by a different process. + * + * @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) { + global $locks; + + unset($locks[$name]); + db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s'", $name, _lock_id()); +} + +/** + * Release all previously acquired locks. + */ +function lock_release_all() { + global $locks; + + $locks = array(); + db_query("DELETE FROM {semaphore} WHERE value = '%s'", _lock_id()); +} + 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 11 Feb 2009 00:22:04 -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,8 +1855,9 @@ function _menu_delete_item($item, $force * saved. */ function menu_link_save(&$item) { - $menu = menu_router_build(); + // Get the router if it's already in memory. + $menu = _menu_router_store(); drupal_alter('menu_link', $item, $menu); // This is the easiest way to handle the unique internal path '', @@ -1960,7 +1975,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 +2036,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 +2411,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: 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 11 Feb 2009 00:22:04 -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 11 Feb 2009 00:22:04 -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"