summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/update/update.compare.inc45
-rw-r--r--modules/update/update.fetch.inc19
-rw-r--r--modules/update/update.module160
3 files changed, 182 insertions, 42 deletions
diff --git a/modules/update/update.compare.inc b/modules/update/update.compare.inc
index f2c183b86..4564a9194 100644
--- a/modules/update/update.compare.inc
+++ b/modules/update/update.compare.inc
@@ -16,9 +16,18 @@
* that logic is only required when preparing the status report, not for
* fetching the available release data.
*
+ * This array is fairly expensive to construct, since it involves a lot of
+ * disk I/O, so we cache the results into the {cache_update} table using the
+ * 'update_project_projects' cache ID. However, since this is not the data
+ * about available updates fetched from the network, it is ok to invalidate it
+ * somewhat quickly. If we keep this data for very long, site administrators
+ * are more likely to see incorrect results if they upgrade to a newer version
+ * of a module or theme but do not visit certain pages that automatically
+ * clear this cache.
+ *
* @see update_process_project_info()
* @see update_calculate_project_data()
- *
+ * @see update_project_cache()
*/
function update_get_projects() {
static $projects = array();
@@ -29,8 +38,8 @@ function update_get_projects() {
// Still empty, so we have to rebuild the cache.
_update_process_info_list($projects, module_rebuild_cache(), 'module');
_update_process_info_list($projects, system_theme_data(), 'theme');
- // Set the projects array into the cache table.
- cache_set('update_project_projects', $projects, 'cache_update', REQUEST_TIME + 3600);
+ // Cache the site's project data for at most 1 hour.
+ _update_cache_set('update_project_projects', $projects, REQUEST_TIME + 3600);
}
}
return $projects;
@@ -223,12 +232,23 @@ function update_process_project_info(&$projects) {
* version (e.g. 5.x-1.5-beta1, 5.x-1.5-beta2, and 5.x-1.5). Development
* snapshots for a given major version are always listed last.
*
+ * The results of this function are expensive to compute, especially on sites
+ * with lots of modules or themes, since it involves a lot of comparisons and
+ * other operations. Therefore, we cache the results into the {cache_update}
+ * table using the 'update_project_data' cache ID. However, since this is not
+ * the data about available updates fetched from the network, it is ok to
+ * invalidate it somewhat quickly. If we keep this data for very long, site
+ * administrators are more likely to see incorrect results if they upgrade to
+ * a newer version of a module or theme but do not visit certain pages that
+ * automatically clear this cache.
+ *
* @param $available
* Array of data about available project releases.
*
* @see update_get_available()
* @see update_get_projects()
* @see update_process_project_info()
+ * @see update_project_cache()
*/
function update_calculate_project_data($available) {
// Retrieve the projects from cache, if present.
@@ -550,8 +570,8 @@ function update_calculate_project_data($available) {
// projects or releases).
drupal_alter('update_status', $projects);
- // Set the projects array into the cache table.
- cache_set('update_project_data', $projects, 'cache_update', REQUEST_TIME + 3600);
+ // Cache the site's update status for at most 1 hour.
+ _update_cache_set('update_project_data', $projects, REQUEST_TIME + 3600);
return $projects;
}
@@ -567,6 +587,13 @@ function update_calculate_project_data($available) {
* administration pages, since we should always recompute the most current
* values on any of those pages.
*
+ * Note: while both of these arrays are expensive to compute (in terms of disk
+ * I/O and some fairly heavy CPU processing), neither of these is the actual
+ * data about available updates that we have to fetch over the network from
+ * updates.drupal.org. That information is stored with the
+ * 'update_available_releases' cache ID -- it needs to persist longer than 1
+ * hour and never get invalidated just by visiting a page on the site.
+ *
* @param $cid
* The cache id of data to return from the cache. Valid options are
* 'update_project_data' and 'update_project_projects'.
@@ -579,15 +606,15 @@ function update_calculate_project_data($available) {
function update_project_cache($cid) {
$projects = array();
- // In some cases, we must clear the cache. Rather than do so on a time
- // basis, we check for specific paths.
+ // On certain paths, we should clear the cache and recompute the projects or
+ // update status of the site to avoid presenting stale information.
$q = $_GET['q'];
$paths = array('admin/build/modules', 'admin/build/themes', 'admin/reports', 'admin/reports/updates', 'admin/reports/status', 'admin/reports/updates/check');
if (in_array($q, $paths)) {
- cache_clear_all($cid, 'cache_update');
+ _update_cache_clear($cid);
}
else {
- $cache = cache_get($cid, 'cache_update');
+ $cache = _update_cache_get($cid);
if (!empty($cache->data) && $cache->expire > REQUEST_TIME) {
$projects = $cache->data;
}
diff --git a/modules/update/update.fetch.inc b/modules/update/update.fetch.inc
index 04bfd00b3..b7970e824 100644
--- a/modules/update/update.fetch.inc
+++ b/modules/update/update.fetch.inc
@@ -27,17 +27,24 @@ function _update_refresh() {
module_load_include('inc', 'update', 'update.compare');
// Since we're fetching new available update data, we want to clear
- // everything in our cache, to ensure we recompute the status. Note that
- // this does not cause update_get_projects() to be recomputed twice in the
- // same page load (e.g. when manually checking) since that function stashes
- // its answer in a static array.
- update_invalidate_cache();
+ // our cache of both the projects we care about, and the current update
+ // status of the site. We do *not* want to clear the cache of available
+ // releases just yet, since that data (even if it's stale) can be useful
+ // during update_get_projects(); for example, to modules that implement
+ // hook_system_info_alter() such as cvs_deploy.
+ _update_cache_clear('update_project_projects');
+ _update_cache_clear('update_project_data');
$available = array();
$data = array();
$site_key = md5($base_url . drupal_get_private_key());
$projects = update_get_projects();
+ // Now that we have the list of projects, we should also clear our cache of
+ // available release data, since even if we fail to fetch new data, we need
+ // to clear out the stale data at this point.
+ _update_cache_clear('update_available_releases');
+
foreach ($projects as $key => $project) {
$url = _update_build_fetch_url($project, $site_key);
$xml = drupal_http_request($url);
@@ -51,7 +58,7 @@ function _update_refresh() {
}
if (!empty($available) && is_array($available)) {
$frequency = variable_get('update_check_frequency', 1);
- cache_set('update_info', $available, 'cache_update', REQUEST_TIME + (60 * 60 * 24 * $frequency));
+ _update_cache_set('update_available_releases', $available, REQUEST_TIME + (60 * 60 * 24 * $frequency));
variable_set('update_last_check', REQUEST_TIME);
watchdog('update', 'Fetched information about all available new releases and updates.', array(), WATCHDOG_NOTICE, l(t('view'), 'admin/reports/updates'));
}
diff --git a/modules/update/update.module b/modules/update/update.module
index 6ed91d621..20f148b7d 100644
--- a/modules/update/update.module
+++ b/modules/update/update.module
@@ -277,9 +277,9 @@ function _update_requirement_check($project, $type) {
function update_cron() {
$frequency = variable_get('update_check_frequency', 1);
$interval = 60 * 60 * 24 * $frequency;
- // Cron should check for updates if there is no update data cached or if the configured
- // update interval has elapsed.
- if (!cache_get('update_info', 'cache_update') || ((REQUEST_TIME - variable_get('update_last_check', 0)) > $interval)) {
+ // Cron should check for updates if there is no update data cached or if the
+ // configured update interval has elapsed.
+ if (!_update_cache_get('update_available_releases') || ((REQUEST_TIME - variable_get('update_last_check', 0)) > $interval)) {
update_refresh();
_update_cron_notify();
}
@@ -291,10 +291,10 @@ function update_cron() {
* Adds a submit handler to the system modules and themes forms, so that if a
* site admin saves either form, we invalidate the cache of available updates.
*
- * @see update_invalidate_cache()
+ * @see _update_cache_clear()
*/
function update_form_system_themes_alter(&$form, $form_state) {
- $form['#submit'][] = 'update_invalidate_cache';
+ $form['#submit'][] = 'update_cache_clear_submit';
}
/**
@@ -303,10 +303,18 @@ function update_form_system_themes_alter(&$form, $form_state) {
* Adds a submit handler to the system modules and themes forms, so that if a
* site admin saves either form, we invalidate the cache of available updates.
*
- * @see update_invalidate_cache()
+ * @see _update_cache_clear()
*/
function update_form_system_modules_alter(&$form, $form_state) {
- $form['#submit'][] = 'update_invalidate_cache';
+ $form['#submit'][] = 'update_cache_clear_submit';
+}
+
+/**
+ * Helper function for use as a form submit callback.
+ */
+function update_cache_clear_submit($form, &$form_state) {
+ // Clear all update module caches.
+ _update_cache_clear();
}
/**
@@ -354,8 +362,7 @@ function update_get_available($refresh = FALSE) {
break;
}
}
- if (!$needs_refresh && ($cache = cache_get('update_info', 'cache_update'))
- && $cache->expire > REQUEST_TIME) {
+ if (!$needs_refresh && ($cache = _update_cache_get('update_available_releases')) && $cache->expire > REQUEST_TIME) {
$available = $cache->data;
}
elseif ($needs_refresh || $refresh) {
@@ -368,24 +375,6 @@ function update_get_available($refresh = FALSE) {
}
/**
- * Implementation of hook_flush_caches().
- *
- * The function update.php (among others) calls this hook to flush the caches.
- * Since we're running update.php, we are likely to install a new version of
- * something, in which case, we want to check for available update data again.
- */
-function update_flush_caches() {
- return array('cache_update');
-}
-
-/**
- * Invalidates any cached data relating to update status.
- */
-function update_invalidate_cache() {
- cache_clear_all('*', 'cache_update', TRUE);
-}
-
-/**
* Wrapper to load the include file and then refresh the release data.
*/
function update_refresh() {
@@ -515,3 +504,120 @@ function _update_project_status_sort($a, $b) {
$b_status = $b['status'] > 0 ? $b['status'] : (-10 * $b['status']);
return $a_status - $b_status;
}
+
+/**
+ * @defgroup update_status_cache Private update status cache system
+ * @{
+ *
+ * We specifically do NOT use the core cache API for saving the fetched data
+ * about available updates. It is vitally important that this cache is only
+ * cleared when we're populating it after successfully fetching new available
+ * update data. Usage of the core cache API results in all sorts of potential
+ * problems that would result in attempting to fetch available update data all
+ * the time, including if a site has a "minimum cache lifetime" (which is both
+ * a minimum and a maximum) defined, or if a site uses memcache or another
+ * plug-able cache system that assumes volatile caches.
+ *
+ * Update module still uses the {cache_update} table, but instead of using
+ * cache_set(), cache_get(), and cache_clear_all(), there are private helper
+ * functions that implement these same basic tasks but ensure that the cache
+ * is not prematurely cleared, and that the data is always stored in the
+ * database, even if memcache or another cache backend is in use.
+ */
+
+/**
+ * Store data in the private update status cache table.
+ *
+ * Note: this function completely ignores the {cache_update}.headers field
+ * since that is meaningless for the kinds of data we're caching.
+ *
+ * @param $cid
+ * The cache ID to save the data with.
+ * @param $data
+ * The data to store.
+ * @param $expire
+ * One of the following values:
+ * - CACHE_PERMANENT: Indicates that the item should never be removed except
+ * by explicitly using _update_cache_clear().
+ * - A Unix timestamp: Indicates that the item should be kept at least until
+ * the given time, after which it will be invalidated.
+ */
+function _update_cache_set($cid, $data, $expire) {
+ $fields = array(
+ 'created' => REQUEST_TIME,
+ 'expire' => $expire,
+ 'headers' => NULL,
+ );
+ if (!is_string($data)) {
+ $fields['data'] = serialize($data);
+ $fields['serialized'] = 1;
+ }
+ else {
+ $fields['data'] = $data;
+ $fields['serialized'] = 0;
+ }
+ db_merge('cache_update')
+ ->key(array('cid' => $cid))
+ ->fields($fields)
+ ->execute();
+}
+
+/**
+ * Retrieve data from the private update status cache table.
+ *
+ * @param $cid
+ * The cache ID to retrieve.
+ * @return
+ * The data for the given cache ID, or NULL if the ID was not found.
+ */
+function _update_cache_get($cid) {
+ $cache = db_query("SELECT data, created, expire, serialized FROM {cache_update} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
+ if (isset($cache->data)) {
+ if ($cache->serialized) {
+ $cache->data = unserialize($cache->data);
+ }
+ }
+ return $cache;
+}
+
+/**
+ * Invalidates cached data relating to update status.
+ *
+ * @param $cid
+ * Optional cache ID of the record to clear from the private update module
+ * cache. If empty, all records will be cleared from the table.
+ */
+function _update_cache_clear($cid = NULL) {
+ $query = db_delete('cache_update');
+ if (!empty($cid)) {
+ $query->condition('cid', $cid);
+ }
+ $query->execute();
+}
+
+/**
+ * Implementation of hook_flush_caches().
+ *
+ * Called from update.php (among others) to flush the caches.
+ * Since we're running update.php, we are likely to install a new version of
+ * something, in which case, we want to check for available update data again.
+ * However, because we have our own caching system, we need to directly clear
+ * the database table ourselves at this point and return nothing, for example,
+ * on sites that use memcache where cache_clear_all() won't know how to purge
+ * this data.
+ *
+ * However, we only want to do this from update.php, since otherwise, we'd
+ * lose all the available update data on every cron run. So, we specifically
+ * check if the site is in MAINTENANCE_MODE == 'update' (which indicates
+ * update.php is running, not update module... alas for overloaded names).
+ */
+function update_flush_caches() {
+ if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
+ _update_cache_clear();
+ }
+ return array();
+}
+
+/**
+ * @} End of "defgroup update_status_cache".
+ */