On 27.02.2010 19:54, minf...@apache.org 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

Reply via email to