Index: database/updates.inc =================================================================== RCS file: /cvs/drupal/drupal/database/updates.inc,v retrieving revision 1.234 diff -u -r1.234 updates.inc --- database/updates.inc 29 May 2006 16:04:41 -0000 1.234 +++ database/updates.inc 31 May 2006 20:36:38 -0000 @@ -146,8 +146,8 @@ } - // Flush the menu cache: - cache_clear_all('menu:', TRUE); + // Invalidate any cached menu items. + variable_set('menu-cache-time', time()); return $ret; } Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.99 diff -u -r1.99 bootstrap.inc --- includes/bootstrap.inc 7 May 2006 00:08:36 -0000 1.99 +++ includes/bootstrap.inc 31 May 2006 20:36:39 -0000 @@ -6,11 +6,16 @@ * Functions that need to be loaded on every Drupal request. */ +define('SECONDS_IN_ONE_DAY', 86400); // (60 * 60 * 24 = 86400) + define('CACHE_PERMANENT', 0); define('CACHE_TEMPORARY', -1); -define('CACHE_DISABLED', 0); -define('CACHE_ENABLED', 1); +define('CACHE_METHOD_NONE', 0); +define('CACHE_METHOD_DATABASE', 1); +define('CACHE_METHOD_MEMCACHED', 2); +define('CACHE_HEADERS', '_HEADERS'); +define('CACHE_MISS', 0); define('WATCHDOG_NOTICE', 0); define('WATCHDOG_WARNING', 1); @@ -150,6 +155,9 @@ */ function conf_init() { global $db_url, $db_prefix, $base_url, $base_path, $base_root, $conf; + global $cache_mode, $memcached_options, $memcache; + + $cache_mode = CACHE_METHOD_DATABASE; $conf = array(); require_once './'. conf_path() .'/settings.php'; @@ -176,6 +184,30 @@ $base_path = '/'; } } + + // Setup access to memcached. + if ($cache_mode == CACHE_METHOD_MEMCACHED) { + if ((array_key_exists('servers', (is_array($memcached_options) ? $memcached_options : array())))) { + $serverlist = $memcached_options['servers']; + foreach ($serverlist as $server) { + list($server, $port) = explode(':', $server); + if (!isset($memcache)) { + $memcache = new Memcache; + if (isset($memcached_options['persist']) and ($memcached_options['persist'])) { + // Use a persistent connection. + $memcache->pconnect($server, $port); + } else { + // Don't use a persistent connection. + $memcache->connect($server, $port); + } + } + else { + $memcache->addServer($server, $port); + } + } + } + } + } /** @@ -311,42 +343,91 @@ * * @param $key * The cache ID of the data to retrieve. + * @param $expired + * The optional timestamp for which we ignore any cached values + * prior to it. */ -function cache_get($key) { - global $user; +function cache_get($key, $expired = 0) { + global $user, $cache_mode, $memcache; - // Garbage collection necessary when enforcing a minimum cache lifetime - $cache_flush = variable_get('cache_flush', 0); - if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) { - // Time to flush old cache data - db_query("DELETE FROM {cache} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush); - variable_set('cache_flush', 0); - } - - $cache = db_fetch_object(db_query("SELECT data, created, headers, expire FROM {cache} WHERE cid = '%s'", $key)); - if (isset($cache->data)) { - // If the data is permanent or we're not enforcing a minimum cache lifetime - // always return the cached data. - if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) { - $cache->data = db_decode_blob($cache->data); - } - // If enforcing a minimum cache lifetime, validate that the data is - // currently valid for this user before we return it by making sure the - // cache entry was created before the timestamp in the current session's - // cache timer. The cache variable is loaded into the $user object by - // sess_read() in session.inc. - else { - if ($user->cache > $cache->created) { - // This cache data is too old and thus not valid for us, ignore it. - return 0; + switch ($cache_mode) { + + case CACHE_METHOD_NONE: + return CACHE_MISS; + break; + + case CACHE_METHOD_DATABASE: + + // Garbage collection necessary when enforcing a minimum cache lifetime + $cache_flush = variable_get('cache_flush', 0); + if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) { + // Time to flush old cache data + db_query("DELETE FROM {cache} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush); + variable_set('cache_flush', 0); } - else { - $cache->data = db_decode_blob($cache->data); + + $cache = db_fetch_object(db_query("SELECT data, created, headers, expire FROM {cache} WHERE cid = '%s'", $key)); + if (isset($cache->data)) { + // If the data is permanent or we're not enforcing a minimum cache lifetime + // always return the cached data. + if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) { + $cache->data = db_decode_blob($cache->data); + } + // If enforcing a minimum cache lifetime, validate that the data is + // currently valid for this user before we return it by making sure the + // cache entry was created before the timestamp in the current session's + // cache timer. The cache variable is loaded into the $user object by + // sess_read() in session.inc. + else { + if ($user->cache > $cache->created) { + // This cache data is too old and thus not valid for us, ignore it. + return CACHE_MISS; + } elseif ($expired > $cache->created) { + return CACHE_MISS; + } else { + $cache->data = db_decode_blob($cache->data); + } + } + return $cache; } - } - return $cache; + return CACHE_MISS; + break; + + case CACHE_METHOD_MEMCACHED: + // memcached will handle expiration for us, so if we get a + // result back, count it as valid. + if (isset($memcache)) { + $keyheaders = $key . CACHE_HEADERS; + $cached = $memcache->get( array($key, $keyheaders) ); + if (isset($cached[$key])) { + // Build our return object. + $cachedata = $cached[$key]; + $cache = new stdClass(); + $cache->data = ''; + $cache->created = 0; + $cache->expire = $expired; + $cache->header = ''; + // Check for a created timestamp; + if (isset($cachedata['created'])) { + $cache->created = $cachedata['created']; + } + // Check if this item is still valid. + if ($cache->created > $cache->expire) { + if (isset($cachedata['data'])) { + $cache->data = $cachedata['data']; + } + // Check for a cached header. + if (isset($cached[$keyheaders])) { + $cache->header = $cached[$keyheaders]; + } + return $cache; + } + } + } + return CACHE_MISS; + break; } - return 0; + } /** @@ -368,12 +449,55 @@ * A string containing HTTP header information for cached pages. */ function cache_set($cid, $data, $expire = CACHE_PERMANENT, $headers = NULL) { - db_lock_table('cache'); - db_query("UPDATE {cache} SET data = %b, created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid); - if (!db_affected_rows()) { - @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', %b, %d, %d, '%s')", $cid, $data, time(), $expire, $headers); + global $cache_mode, $memcache; + + switch ($cache_mode) { + + case CACHE_METHOD_NONE: + break; + + case CACHE_METHOD_DATABASE: + + db_lock_table('cache'); + db_query("UPDATE {cache} SET data = %b, created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid); + if (!db_affected_rows()) { + @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', %b, %d, %d, '%s')", $cid, $data, time(), $expire, $headers); + } + db_unlock_tables(); + break; + + case CACHE_METHOD_MEMCACHED: + + if (isset($memcache)) { + // Determine the timeout as used by memcached. + $time = 0; + switch ($expire) { + case CACHE_PERMANENT: + $time = 0; + break; + case CACHE_TEMPORARY: + $life = variable_get('cache_lifetime', 0); + if ($life == 0) { + $time = 0; // treat it as CACHE_PERMANENT + } else { + $time = time() + $life; // set the expire time to now + lifetime + } + break; + default: + $time = $expire; // use supplied expire time + break; + } + // Unconditionally set the data, this will update any old values. + $cachedata = array( 'data' => $data, 'created' => time() ); + $memcache->set($cid, $cachedata, 0, $time); + if (isset($headers)) { + $memcache->set($cid . CACHE_HEADERS, $headers, 0, $time); + } + } + break; + } - db_unlock_tables(); + } /** @@ -388,41 +512,57 @@ * complete ID. */ function cache_clear_all($cid = NULL, $wildcard = false) { - global $user; + global $user, $cache_mode, $memcache; - if (empty($cid)) { - if (variable_get('cache_lifetime', 0)) { - // We store the time in the current user's $user->cache variable which - // will be saved into the sessions table by sess_write(). We then - // simulate that the cache was flushed for this user by not returning - // cached data that was cached before the timestamp. - $user->cache = time(); + switch ($cache_mode) { - $cache_flush = variable_get('cache_flush', 0); - if ($cache_flush == 0) { - // This is the first request to clear the cache, start a timer. - variable_set('cache_flush', time()); - } - else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) { - // Clear the cache for everyone, cache_flush_delay seconds have - // passed since the first request to clear the cache. - db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); - variable_set('cache_flush', 0); + case CACHE_METHOD_NONE: + break; + + case CACHE_METHOD_DATABASE: + if (empty($cid)) { + if (variable_get('cache_lifetime', 0)) { + // We store the time in the current user's $user->cache variable which + // will be saved into the sessions table by sess_write(). We then + // simulate that the cache was flushed for this user by not returning + // cached data that was cached before the timestamp. + $user->cache = time(); + + $cache_flush = variable_get('cache_flush', 0); + if ($cache_flush == 0) { + // This is the first request to clear the cache, start a timer. + variable_set('cache_flush', time()); + } + else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) { + // Clear the cache for everyone, cache_flush_delay seconds have + // passed since the first request to clear the cache. + db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); + variable_set('cache_flush', 0); + } + } + else { + // No minimum cache lifetime, flush all temporary cache entries now. + db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); + } } - } - else { - // No minimum cache lifetime, flush all temporary cache entries now. - db_query("DELETE FROM {cache} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time()); - } - } - else { - if ($wildcard) { - db_query("DELETE FROM {cache} WHERE cid LIKE '%%%s%%'", $cid); - } - else { - db_query("DELETE FROM {cache} WHERE cid = '%s'", $cid); - } + else { + db_query("DELETE FROM {cache} WHERE cid = '%s'", $cid); + } + break; + + case CACHE_METHOD_MEMCACHED: + // We don't need to flush everything since memcached handles that for us. + // We can still purge specific values when requested. + if (isset($memcache)) { + if (isset($cid)) { + $memcache->delete($cid); + $memcache->delete($cid . CACHE_HEADERS); + } + } + break; + } + } /** @@ -796,3 +936,4 @@ unicode_check(); $theme = ''; } + Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.122 diff -u -r1.122 menu.inc --- includes/menu.inc 21 May 2006 09:18:47 -0000 1.122 +++ includes/menu.inc 31 May 2006 20:36:40 -0000 @@ -204,16 +204,16 @@ if (!isset($_menu['items'])) { // _menu_build() may indirectly call this function, so prevent infinite loops. - $_menu['items'] = array(); $cid = "menu:$user->uid:$locale"; - if ($cached = cache_get($cid)) { + if ($cached = cache_get($cid, variable_get('menu-cache-time', 0))) { $_menu = unserialize($cached->data); } - else { + + if (!isset($_menu['items'])) { _menu_build(); // Cache the menu structure for this user, to expire after one day. - cache_set($cid, serialize($_menu), time() + (60 * 60 * 24)); + cache_set($cid, serialize($_menu), $time + SECONDS_IN_ONE_DAY); } // Make sure items that cannot be cached are added. @@ -586,8 +586,8 @@ function menu_rebuild() { // Clear the page cache, so that changed menus are reflected for anonymous users. cache_clear_all(); - // Also clear the menu cache. - cache_clear_all('menu:', TRUE); + // Invalidate any cached menu items. + variable_set('menu-cache-time', time()); _menu_build(); Index: modules/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter.module,v retrieving revision 1.124 diff -u -r1.124 filter.module --- modules/filter.module 7 May 2006 00:08:36 -0000 1.124 +++ modules/filter.module 31 May 2006 20:36:41 -0000 @@ -383,7 +383,9 @@ db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $form_values['format']); db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $form_values['format']); - cache_clear_all('filter:'. $form_values['format'], true); + // Invalidate any cached filters. + variable_set('filter-cache-time', time()); + drupal_set_message(t('Deleted input format %format.', array('%format' => theme('placeholder', $form_values['name'])))); return 'admin/filters'; @@ -522,7 +524,8 @@ db_query("UPDATE {filter_formats} SET cache = %d, name='%s', roles = '%s' WHERE format = %d", $cache, $name, $roles, $format); - cache_clear_all('filter:'. $format, true); + // Invalidate any cached filters. + variable_set('filter-cache-time', time()); // If a new filter was added, return to the main list of filters. Otherwise, stay on edit filter page to show new changes. if ($new) { @@ -580,7 +583,8 @@ } drupal_set_message(t('The filter ordering has been saved.')); - cache_clear_all('filter:'. $form_values['format'], true); + // Invalidate any cached filters. + variable_set('filter-cache-time', time()); } /** @@ -745,7 +749,7 @@ // Check for a cached version of this piece of text. $id = 'filter:'. $format .':'. md5($text); - if ($cached = cache_get($id)) { + if ($cached = cache_get($id, variable_get('filter-cache-time', 0))) { return $cached->data; } @@ -771,7 +775,7 @@ // Store in cache with a minimum expiration time of 1 day. if ($cache) { - cache_set($id, $text, time() + (60 * 60 * 24)); + cache_set($id, $text, time() + SECONDS_IN_ONE_DAY); } } else { Index: modules/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user.module,v retrieving revision 1.626 diff -u -r1.626 user.module --- modules/user.module 29 May 2006 13:02:14 -0000 1.626 +++ modules/user.module 31 May 2006 20:36:43 -0000 @@ -1438,8 +1438,8 @@ unset($form_values['_account'], $form_values['submit'], $form_values['delete'], $form_values['form_id'], $form_values['_category']); user_module_invoke('submit', $form_values, $account, $category); user_save($account, $form_values, $category); - // Delete that user's menu cache. - cache_clear_all('menu:'. $account->uid, TRUE); + // Invalidate any cached menu items. + variable_set('menu-cache-time', time()); drupal_set_message(t('The changes have been saved.')); return 'user/'. $account->uid; } Index: sites/default/settings.php =================================================================== RCS file: /cvs/drupal/drupal/sites/default/settings.php,v retrieving revision 1.28 diff -u -r1.28 settings.php --- sites/default/settings.php 7 May 2006 00:08:36 -0000 1.28 +++ sites/default/settings.php 31 May 2006 20:36:43 -0000 @@ -147,3 +147,30 @@ # 'anonymous' => 'Visitor' # ); + +/** + * Set caching preference. + * Choose one of the following cache methods. + * + * CACHE_METHOD_NONE = 0 + * CACHE_METHOD_DATABASE = 1 + * CACHE_METHOD_MEMCACHED = 2 + */ +$cache_mode = CACHE_METHOD_DATABASE; + +/** + * When using CACHE_METHOD_MEMCACHED, uncomment and adjust + * the following settings. + * + * $memcached_options = array( + * 'servers' => array( '127.0.0.1:11212' ), + * // Add additional servers as need. + * 'persist' => true + * // Establish a persistant connection to the cache server. + * ); + */ +#$memcached_options = array( +# 'servers' => array( '127.0.0.1:11211' ), +# 'persist' => true +#); +