diff --git includes/bootstrap.inc includes/bootstrap.inc index 1c13c6f..85b4dba 100644 --- includes/bootstrap.inc +++ includes/bootstrap.inc @@ -732,25 +732,14 @@ function drupal_get_filename($type, $name, $filename = NULL) { function variable_initialize($conf = array()) { // NOTE: caching the variables improves performance by 20% when serving // cached pages. - if ($cached = cache_get('variables', 'cache_bootstrap')) { - $variables = $cached->data; + $cache = &drupal_static('variable'); + if ($cached = cache_get('variable_cache', 'cache_bootstrap')) { + $cache = $cached->data; } else { - // Cache miss. Avoid a stampede. - $name = 'variable_init'; - if (!lock_acquire($name, 1)) { - // Another request is building the variable cache. - // Wait, then re-run this function. - lock_wait($name); - return variable_initialize($conf); - } - else { - // Proceed with variable rebuild. - $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed()); - cache_set('variables', $variables, 'cache_bootstrap'); - lock_release($name); - } + $cache = array('variables' => array(), 'defaults' => array()); } + $variables = $cache['variables']; foreach ($conf as $name => $value) { $variables[$name] = $value; @@ -779,11 +768,64 @@ function variable_initialize($conf = array()) { */ function variable_get($name, $default = NULL) { global $conf; + if (isset($conf[$name])) { + return $conf[$name]; + } + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['cache'] = &drupal_static('variable'); + } + $cache = &$drupal_static_fast['cache']; + + // $cache is populated in variable_initialize() for each request. If it's not + // available here we are in a lower bootstrap phase where the database and + // cache system may not be available yet. + if (isset($cache)) { + // First check the cached list of variables that should use defaults. + if (isset($cache['defaults'][$name])) { + return $default; + } + // If the variable is neither in $conf, nor in $cache['defaults'] then + // query the database. + else { + // Check if variables are cacheable for this request, if so record the + // current size of the variable cache and set a flag to write the cache + // later. + if (variable_is_cacheable() && !isset($cache['write_cache'])) { + $cache['write_cache'] = array( + 'variables_count' => count($cache['variables']), + 'defaults_count' => count($cache['defaults']), + ); + } + $result = db_query('SELECT value FROM {variable} WHERE name = :name', array(':name' => $name))->fetchField(); + if ($result) { + $value = unserialize($result); + $conf[$name] = $cache['variables'][$name] = $value; + } + else { + $cache['defaults'][$name] = 1; + } + } + } return isset($conf[$name]) ? $conf[$name] : $default; } /** + * Determine whether variables can be cached during the request. + */ +function variable_is_cacheable($allow_caching = NULL) { + $allow_caching_static = &drupal_static(__FUNCTION__, TRUE); + if (isset($allow_caching)) { + $allow_caching_static = $allow_caching; + } + + return $allow_caching_static && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') + && !drupal_is_cli(); +} + +/** * Sets a persistent variable. * * Case-sensitivity of the variable_* functions depends on the database @@ -803,8 +845,10 @@ function variable_set($name, $value) { global $conf; db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute(); - - cache_clear_all('variables', 'cache_bootstrap'); + // Set the variable cache to empty arrays, this ensures that requests trying + // set the cache immediately after the variable is written will not do so + // with a stale cache. + cache_set('variable_cache', array('variables' => array(), 'defaults' => array()), 'cache_bootstrap'); $conf[$name] = $value; } @@ -828,7 +872,9 @@ function variable_del($name) { db_delete('variable') ->condition('name', $name) ->execute(); - cache_clear_all('variables', 'cache_bootstrap'); + // Set the cache to empty arrays, this ensures that requests setting the + // cache immediately after this will not do so with stale cache entries. + cache_set('variable_cache', array('variables' => array(), 'defaults' => array()), 'cache_bootstrap'); unset($conf[$name]); } @@ -2252,10 +2298,6 @@ function _drupal_bootstrap_database() { function _drupal_bootstrap_variables() { global $conf; - // Initialize the lock system. - require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc'); - lock_initialize(); - // Load variables from the database, but do not overwrite variables set in settings.php. $conf = variable_initialize(isset($conf) ? $conf : array()); // Load bootstrap modules. @@ -2269,6 +2311,10 @@ function _drupal_bootstrap_variables() { function _drupal_bootstrap_page_header() { bootstrap_invoke_all('boot'); + // Prepare for non-cached page workflow. + require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc'); + lock_initialize(); + if (!drupal_is_cli()) { ob_start(); drupal_page_header(); diff --git includes/common.inc includes/common.inc index 03b0de4..ac8c2e5 100644 --- includes/common.inc +++ includes/common.inc @@ -2564,6 +2564,7 @@ function drupal_page_footer() { _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); drupal_cache_system_paths(); module_implements_write_cache(); + variable_write_cache(); system_run_automated_cron(); } @@ -2590,6 +2591,48 @@ function drupal_exit($destination = NULL) { } /** + * Update the variable cache after a cache miss. + */ +function variable_write_cache() { + $cache = &drupal_static('variable'); + if (!empty($cache['write_cache']) && variable_is_cacheable()) { + // Get a fresh copy of the variable cache from cache_get(), this will + // include any changes made by other requests between variable_initialize() + // and variable_write_cache(). + if ($cached = cache_get('variable_cache', 'cache_bootstrap')) { + $current_cache = $cached->data; + + // Compare the current cache sizes to those requested during + // variable_initialize(), if the current size is smaller, another + // request must have reset the cache in between. + if (count($current_cache['variables']) < $cache['write_cache']['variables_count'] || count($current_cache['defaults']) < $cache['write_cache']['defaults_count']) { + // Return early since there's nothing to do here. + return FALSE; + } + else { + // Merge the variables built during this request with the latest + // version of the cache, this ensures that this request will not + // undo caching of variables by another process. Compare the size of + // the cache before and after this process to avoid trying to set + // cache entries redundantly. + $count = count($cache['variables']) + count($cache['defaults']); + + $cache += $current_cache; + // If the size of the data to be cached has not increased, there is no + // point writing to the cache again. + if ($count && $count < count($cache['variables'] + count($cache['defaults']))) { + return FALSE; + } + } + } + // If the cache is empty, or if it has not changed since the beginning of + // the request, write the new cache entry. + unset($cache['write_cache']); + cache_set('variable_cache', $cache, 'cache_bootstrap'); + } +} + +/** * Form an associative array from a linear array. * * This function walks through the provided array and constructs an associative @@ -5003,6 +5046,10 @@ function drupal_cron_run() { // Prevent session information from being saved while cron is running. drupal_save_session(FALSE); + // Avoid caching variables during cron runs, this prevents variables used + // only during cron from being loaded into memory on other requests. + variable_is_cacheable(FALSE); + // Force the current user to anonymous to ensure consistent permissions on // cron runs. $original_user = $GLOBALS['user']; diff --git modules/system/system.module modules/system/system.module index b5dfc54..bd24635 100644 --- modules/system/system.module +++ modules/system/system.module @@ -2722,6 +2722,9 @@ function system_default_region($theme) { * @ingroup forms */ function system_settings_form($form) { + // Prevent caching of variables when on system settings pages, to avoid the + // cache being polluted with variables that are not in frequent use. + variable_is_cacheable(FALSE); $form['actions']['#type'] = 'actions'; $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));