Index: includes/bootstrap.inc =================================================================== --- includes/bootstrap.inc (revision 7577) +++ includes/bootstrap.inc (working copy) @@ -1054,6 +1054,8 @@ 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 =================================================================== --- includes/lock.inc (revision 0) +++ includes/lock.inc (revision 0) @@ -0,0 +1,234 @@ + $lock['expire']) { + // We check two conditions to prevent a race condition where another + // request acquired the lock and set a new expire time. We add a small + // number to $expire to avoid errors with float to string conversion. + db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s' AND expire <= %f", $name, $lock['value'], 0.0001 + $expire); + return (bool)db_affected_rows(); + } + return FALSE; +} + +/** + * Wait for a lock to be available. + * + * This function may be called in a request that fails to acquire a desired + * lock. This will block further execution until the lock is available or the + * specified delay in seconds is reached. This should not be used with locks + * that are acquired very frequently, since the lock is likely to be acquired + * again by a different request during the sleep(). + * + * @param $name + * The name of the lock. + * @param $delay + * The maximum number of seconds to wait, as an integer. + * @return + * TRUE if the lock holds, FALSE if it is available. + */ +function lock_wait($name, $delay = 30) { + + while ($delay--) { + // 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); + if (lock_may_be_available($name)) { + // No longer need to wait. + return FALSE; + } + } + // The caller must still wait longer to get the lock. + return TRUE; +} + +/** + * Release a lock previously acquired by lock_acquire(). + * + * This will release the named lock if it is still held by the current request. + * + * @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($lock_id = NULL) { + global $locks; + + $locks = array(); + if (empty($lock_id)) { + $lock_id = _lock_id(); + } + + db_query("DELETE FROM {semaphore} WHERE value = '%s'", _lock_id()); +} + +/** + * @} End of "defgroup locks". + */ Index: includes/menu.inc =================================================================== --- includes/menu.inc (revision 7577) +++ includes/menu.inc (working copy) @@ -1669,15 +1669,28 @@ * is different and leaves stale data in the menu tables. */ function menu_rebuild() { - variable_del('menu_rebuild_needed'); + if (!lock_acquire('menu_rebuild')) { + // Wait for another request that is already doing this work. + // We choose to block here since otherwise the router item may not + // be avaiable in menu_execute_active_handler() resulting in a 404. + lock_wait('menu_rebuild'); + return FALSE; + } + $menu = menu_router_build(TRUE); _menu_navigation_links_rebuild($menu); // Clear the menu, page and block caches. menu_cache_clear_all(); _menu_clear_page_cache(); + if (defined('MAINTENANCE_MODE')) { variable_set('menu_rebuild_needed', TRUE); } + else { + variable_del('menu_rebuild_needed'); + } + lock_release('menu_rebuild'); + return TRUE; } /** Index: modules/system/system.install =================================================================== --- modules/system/system.install (revision 7577) +++ modules/system/system.install (working copy) @@ -956,6 +956,31 @@ 'primary key' => array('mlid'), ); + $schema['semaphore'] = array( + 'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.', + '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( @@ -2598,6 +2623,37 @@ } /** + * Add semaphore table. + */ +function system_update_6054() { + $ret = array(); + + $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-6.x-extra" * The next series of updates should start at 7000. */ Index: modules/locale/locale.module =================================================================== --- modules/locale/locale.module (revision 7577) +++ modules/locale/locale.module (working copy) @@ -345,7 +345,7 @@ if ($cache = cache_get('locale:'. $langcode, 'cache')) { $locale_t[$langcode] = $cache->data; } - else { + elseif (lock_acquire('locale_cache_' . $langcode)) { // Refresh database stored cache of translations for given language. // We only store short strings used in current version, to improve // performance and consume less memory. @@ -354,6 +354,7 @@ $locale_t[$langcode][$data->source] = (empty($data->translation) ? TRUE : $data->translation); } cache_set('locale:'. $langcode, $locale_t[$langcode]); + lock_release('locale_cache_' . $langcode); } } }