On 27.02.2010 19:54, [email protected] wrote:
Author: minfrin
Date: Sat Feb 27 18:54:40 2010
New Revision: 917013
URL: http://svn.apache.org/viewvc?rev=917013&view=rev
Log:
mod_cache: Introduce the thundering herd lock, a mechanism to keep
the flood of requests at bay that strike a backend webserver as
a cached entity goes stale.
+1: minfrin, jim, pgollucci
Modified:
httpd/httpd/branches/2.2.x/CHANGES
httpd/httpd/branches/2.2.x/STATUS
httpd/httpd/branches/2.2.x/docs/manual/mod/mod_cache.xml
httpd/httpd/branches/2.2.x/modules/cache/cache_storage.c
httpd/httpd/branches/2.2.x/modules/cache/cache_util.c
httpd/httpd/branches/2.2.x/modules/cache/mod_cache.c
httpd/httpd/branches/2.2.x/modules/cache/mod_cache.h
Modified: httpd/httpd/branches/2.2.x/modules/cache/cache_util.c
URL:
http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/modules/cache/cache_util.c?rev=917013&r1=917012&r2=917013&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/modules/cache/cache_util.c (original)
+++ httpd/httpd/branches/2.2.x/modules/cache/cache_util.c Sat Feb 27 18:54:40
2010
@@ -22,6 +22,8 @@
/* -------------------------------------------------------------- */
+extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
+
extern module AP_MODULE_DECLARE_DATA cache_module;
/* Determine if "url" matches the hostname, scheme and port and path
@@ -164,9 +166,182 @@
return apr_time_sec(current_age);
}
+/**
+ * Try obtain a cache wide lock on the given cache key.
+ *
+ * If we return APR_SUCCESS, we obtained the lock, and we are clear to
+ * proceed to the backend. If we return APR_EEXISTS, then the lock is
+ * already locked, someone else has gone to refresh the backend data
+ * already, so we must return stale data with a warning in the mean
+ * time. If we return anything else, then something has gone pear
+ * shaped, and we allow the request through to the backend regardless.
+ *
+ * This lock is created from the request pool, meaning that should
+ * something go wrong and the lock isn't deleted on return of the
+ * request headers from the backend for whatever reason, at worst the
+ * lock will be cleaned up when the request dies or finishes.
+ *
+ * If something goes truly bananas and the lock isn't deleted when the
+ * request dies, the lock will be trashed when its max-age is reached,
+ * or when a request arrives containing a Cache-Control: no-cache. At
+ * no point is it possible for this lock to permanently deny access to
+ * the backend.
+ */
+CACHE_DECLARE(apr_status_t) ap_cache_try_lock(cache_server_conf *conf,
+ request_rec *r, char *key) {
+ apr_status_t status;
+ const char *lockname;
+ const char *path;
+ char dir[5];
+ apr_time_t now = apr_time_now();
+ apr_finfo_t finfo;
+ apr_file_t *lockfile;
+ void *dummy;
+
+ finfo.mtime = 0;
+
+ if (!conf || !conf->lock || !conf->lockpath) {
+ /* no locks configured, leave */
+ return APR_SUCCESS;
+ }
+
+ /* lock already obtained earlier? if so, success */
+ apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool);
+ if (dummy) {
+ return APR_SUCCESS;
+ }
+
+ /* create the key if it doesn't exist */
+ if (!key) {
+ cache_generate_key(r, r->pool,&key);
+ }
+
+ /* create a hashed filename from the key, and save it for later */
+ lockname = ap_cache_generate_name(r->pool, 0, 0, key);
+
+ /* lock files represent discrete just-went-stale URLs "in flight", so
+ * we support a simple two level directory structure, more is overkill.
+ */
+ dir[0] = '/';
+ dir[1] = lockname[0];
+ dir[2] = '/';
+ dir[3] = lockname[1];
+ dir[4] = 0;
+
+ /* make the directories */
+ path = apr_pstrcat(r->pool, conf->lockpath, dir, NULL);
+ if (APR_SUCCESS != (status = apr_dir_make_recursive(path,
+ APR_UREAD|APR_UWRITE|APR_UEXECUTE, r->pool))) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
+ "Could not create a cache lock directory: %s",
+ path);
+ return status;
+ }
+ lockname = apr_pstrcat(r->pool, path, "/", lockname, NULL);
+ apr_pool_userdata_set(lockname, CACHE_LOCKNAME_KEY, NULL, r->pool);
+
+ /* is an existing lock file too old? */
+ status = apr_stat(&finfo, lockname,
+ APR_FINFO_MTIME | APR_FINFO_NLINK, r->pool);
+ if (!(APR_STATUS_IS_ENOENT(status))&& APR_SUCCESS != status) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, APR_EEXIST, r->server,
+ "Could not stat a cache lock file: %s",
+ lockname);
+ return status;
+ }
+ if (APR_SUCCESS == status&& ((now - finfo.mtime)> conf->lockmaxage)
+ || (now< finfo.mtime)) {
Compiler warns:
cache_util.c: In function 'ap_cache_try_lock':
cache_util.c:253: warning: suggest parentheses around && within ||
and indeed trunk has additional parentheses surrounding the "or" clause:
If ((status == APR_SUCCESS) && (((now - finfo.mtime) > conf->lockmaxage)
|| (now < finfo.mtime))) {
It seems the backport missed something.
+ ap_log_error(APLOG_MARK, APLOG_INFO, status, r->server,
+ "Cache lock file for '%s' too old, removing: %s",
+ r->uri, lockname);
+ apr_file_remove(lockname, r->pool);
+ }
+
+ /* try obtain a lock on the file */
+ if (APR_SUCCESS == (status = apr_file_open(&lockfile, lockname,
+ APR_WRITE | APR_CREATE | APR_EXCL | APR_DELONCLOSE,
+ APR_UREAD | APR_UWRITE, r->pool))) {
+ apr_pool_userdata_set(lockfile, CACHE_LOCKFILE_KEY, NULL, r->pool);
+ }
+ return status;
+
+}
Regards,
Rainer