This patch implements copying a file in the background so the client initiating the caching can get the file delivered by read-while-caching instead of having to wait for the file to finish.

I'll attach it to bug #39380 as well, with less comments.

The method used here is rather crude, but works well enough in practice. It should suffice as a first step of implementing this functionality.

Known missing features:
* Documentation for the CacheMinBGSize parameter, the minimum file
  size to to do background caching. Typically set to what your backend
  can deliver in approx 250ms at normal load (given 200ms sleep loop).
* It doesn't set the stacksize for the background thread, it made
  stuff unloadable on AIX which probably means some symbol is missing
  in an export table somewhere.
* Testing of the forked variation. This has only had testing with the
  worker MPM on Unix.

Known areas of possible improvements:
* Figure out why the cleanup-function isn't run before the fd's are
  closed so the private pool can be removed.
* I suppose it's possible to use cross-threads-fd's with some
  setaside-magic instead of open new fd's in the bgcopy thread.
* Experiment with a separate copy-files-thread spawned at
  initialization for threaded environments.
* The forked thingie could probably use a few cleanups.

In practice I don't think those improvements will give much in terms of performance but it sure would be more elegant :)

/Nikke
--
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 Niklas Edmundsson, Admin @ {acc,hpc2n}.umu.se      |     [EMAIL PROTECTED]
---------------------------------------------------------------------------
 "It's funny how the Earth never opens up and swallows you when you want it to."
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
--- mod_disk_cache.c.ls-rwc-fixups      2006-10-08 19:17:31.000000000 +0200
+++ mod_disk_cache.c    2006-10-08 19:11:40.000000000 +0200
@@ -22,6 +22,8 @@
 #include "util_filter.h"
 #include "util_script.h"
 #include "util_charset.h"
+#include "ap_mpm.h"
+
 
 /*
  * mod_disk_cache: Disk Based HTTP 1.1 Cache.
@@ -1677,6 +1679,272 @@ static apr_status_t copy_body(apr_pool_t
 }
 
 
+/* Provide srcfile and srcinfo containing
+   APR_FINFO_INODE|APR_FINFO_MTIME to make sure we have opened the right file
+   (someone might have just replaced it which messes up things).
+*/
+static apr_status_t copy_body_nofd(apr_pool_t *p, const char *srcfile, 
+                                   apr_off_t srcoff, apr_finfo_t *srcinfo,
+                                   const char *destfile, apr_off_t destoff, 
+                                   apr_off_t len)
+{
+    apr_status_t rc;
+    apr_file_t *srcfd, *destfd;
+    apr_finfo_t finfo;
+
+    rc = apr_file_open(&srcfd, srcfile, APR_READ | APR_BINARY, 0, p);
+    if(rc != APR_SUCCESS) {
+        return rc;
+    }
+    rc = apr_file_info_get(&finfo, APR_FINFO_INODE|APR_FINFO_MTIME, srcfd);
+    if(rc != APR_SUCCESS) {
+        return rc;
+    }
+    if(srcinfo->inode != finfo.inode || srcinfo->mtime < finfo.mtime) {
+        return APR_EGENERAL;
+    }
+
+    rc = apr_file_open(&destfd, destfile, APR_WRITE | APR_BINARY, 0, p);
+    if(rc != APR_SUCCESS) {
+        return rc;
+    }
+
+    rc = copy_body(p, srcfd, srcoff, destfd, destoff, len);
+    apr_file_close(srcfd);
+    if(rc != APR_SUCCESS) {
+        apr_file_close(destfd);
+        return rc;
+    }
+
+    return apr_file_close(destfd);
+}
+
+
+#if APR_HAS_THREADS
+static apr_status_t bgcopy_thread_cleanup(void *data)
+{
+    copyinfo *ci = data;
+    apr_status_t rc, ret;
+    apr_pool_t *p;
+
+    /* FIXME: Debug */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ci->s,
+                 "disk_cache: bgcopy_thread_cleanup: %s -> %s",
+                 ci->srcfile, ci->destfile);
+
+    rc = apr_thread_join(&ret, ci->t);
+    if(rc != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rc, ci->s,
+                     "disk_cache: bgcopy_thread_cleanup: apr_thread_join "
+                     "failed %s -> %s", ci->srcfile, ci->destfile);
+        return rc;
+    }
+    if(ret != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, ret, ci->s,
+                     "disk_cache: Background caching body %s -> %s failed",
+                     ci->srcfile, ci->destfile);
+    }
+
+    /* FIXME: Debug */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ci->s,
+                 "disk_cache: bgcopy_thread_cleanup: SUCCESS %s -> %s",
+                 ci->srcfile, ci->destfile);
+
+    /* Destroy our private pool */
+    p = ci->pool;
+    apr_pool_destroy(p);
+
+    return APR_SUCCESS;
+}
+
+
+static void *bgcopy_thread(apr_thread_t *t, void *data)
+{
+    copyinfo *ci = data;
+    apr_pool_t *p;
+    apr_status_t rc;
+
+    p = apr_thread_pool_get(t);
+
+    /* FIXME: Debug */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ci->s,
+                 "disk_cache: bgcopy_thread: start %s -> %s",
+                 ci->srcfile, ci->destfile);
+
+    rc = copy_body_nofd(p, ci->srcfile, ci->srcoff, &(ci->srcinfo), 
+                        ci->destfile, ci->destoff, ci->len);
+
+    if(rc != APR_SUCCESS) {
+        apr_file_remove(ci->destfile, p);
+    }
+
+    /* FIXME: Debug */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ci->s,
+                 "disk_cache: bgcopy_thread: done %s -> %s",
+                 ci->srcfile, ci->destfile);
+
+    apr_thread_exit(t, rc);
+    return NULL;
+}
+#endif /* APR_HAS_THREADS */
+
+
+#if APR_HAS_FORK
+static apr_status_t bgcopy_child_cleanup(void *data) {
+    copyinfo *ci = data;
+    int status;
+    apr_exit_why_e why;
+    apr_pool_t *p;
+
+    apr_proc_wait(ci->proc, &status, &why, APR_WAIT);
+    if(why == APR_PROC_EXIT) {
+        if(status != APR_SUCCESS) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, status, ci->s,
+                         "disk_cache: Background caching body %s -> %s failed",
+                         ci->srcfile, ci->destfile);
+            return APR_SUCCESS;
+        }
+    }
+    else if(status & (APR_PROC_SIGNAL | APR_PROC_SIGNAL_CORE) ) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, ci->s,
+                     "disk_cache: Background caching body %s -> %s failed, "
+                     "caught signal %d", ci->srcfile, ci->destfile, status);
+        return APR_SUCCESS;
+    }
+
+    /* Destroy our private pool */
+    p = ci->pool;
+    apr_pool_destroy(p);
+
+    return APR_SUCCESS;
+}
+#endif /* APR_HAS_FORK */
+
+
+static apr_status_t do_bgcopy(apr_file_t *srcfd, apr_off_t srcoff, 
+                              apr_file_t *destfd, apr_off_t destoff, 
+                              apr_off_t len, conn_rec *c)
+{
+    copyinfo *ci;
+    apr_status_t rv;
+    apr_pool_t *newpool;
+    const char *srcfile, *destfile;
+    int mpm_query_info;
+
+    /* It seems pool gets destroyed (ie. fd's closed) before our cleanup 
+       function is called when an error occurs (a dropped connection, for
+       example), so we need a pool of our own.
+     */
+    rv = apr_pool_create(&newpool, NULL);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    ci = apr_palloc(newpool, sizeof(*ci));
+    if(ci == NULL) {
+        apr_pool_destroy(newpool);
+        return APR_ENOMEM;
+    }
+
+    rv = apr_file_name_get(&srcfile, srcfd);
+    if(rv != APR_SUCCESS) {
+        return rv;
+    }
+    rv = apr_file_info_get(&(ci->srcinfo), APR_FINFO_INODE|APR_FINFO_MTIME,
+                           srcfd);
+    if(rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_file_name_get(&destfile, destfd);
+    if(rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    ci->pool = newpool;
+    ci->srcfile = apr_pstrdup(newpool, srcfile);
+    ci->srcoff = srcoff;
+    ci->destfile = apr_pstrdup(newpool, destfile);
+    ci->destoff = destoff;
+    ci->len = len;
+    ci->s = c->base_server;
+
+#if APR_HAS_THREADS
+    if(ap_mpm_query(AP_MPMQ_IS_THREADED, &mpm_query_info) == APR_SUCCESS) {
+        apr_threadattr_t *ta;
+        apr_thread_t *t;
+        rv = apr_threadattr_create(&ta, newpool);
+        if(rv != APR_SUCCESS) {
+            apr_pool_destroy(newpool);
+            return rv;
+        }
+
+        apr_threadattr_detach_set(ta, FALSE);
+
+        /* FIXME: This makes module unloadable on AIX */
+#if 0
+#ifdef AP_MPM_WANT_SET_STACKSIZE
+        if (ap_thread_stacksize != 0) {
+            apr_threadattr_stacksize_set(ta, ap_thread_stacksize);
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, c->base_server,
+                    "disk_cache: BG thread stacksize set to %"
+                    APR_SIZE_T_FMT, ap_thread_stacksize);
+        }
+#endif /* AP_MPM_WANT_SET_STACKSIZE */
+#endif /* 0 */
+
+        rv = apr_thread_create (&t, ta, bgcopy_thread, ci, newpool);
+        if (rv != APR_SUCCESS) {
+            apr_pool_destroy(newpool);
+            return rv;
+        }
+        ci->t = t;
+
+        apr_pool_cleanup_register(c->pool, ci, 
+                                  bgcopy_thread_cleanup, 
apr_pool_cleanup_null);
+    }
+    else
+#endif /* APR_HAS_THREADS */
+#if APR_HAS_FORK
+    if(ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_query_info) == APR_SUCCESS) {
+        ci->proc = apr_palloc(newpool, sizeof(apr_proc_t));
+        if(ci->proc == NULL) {
+            apr_pool_destroy(newpool);
+            return APR_ENOMEM;
+        }
+        rv = apr_proc_fork(ci->proc, newpool);
+        if(rv == APR_INCHILD) {
+            /* Child */
+            rv = copy_body_nofd(ci->pool, ci->srcfile, ci->srcoff, 
+                                &(ci->srcinfo), ci->destfile, ci->destoff, 
+                                ci->len);
+            if(rv != APR_SUCCESS) {
+                apr_file_remove(ci->destfile, ci->pool);
+            }
+            exit(rv);
+        }
+        else if(rv == APR_INPARENT) {
+            apr_pool_cleanup_register(c->pool, ci, 
+                                      bgcopy_child_cleanup, 
+                                      apr_pool_cleanup_null);
+        }
+        else {
+            return rv;
+        }
+    }
+    else 
+#endif /* APR_HAS_FORK */
+    if(1)
+    {
+        rv = copy_body(newpool, srcfd, ci->srcoff, destfd, ci->destoff,
+                       ci->len);
+        apr_pool_destroy(newpool);
+    }
+
+    return rv;
+}
+
+
 static apr_status_t replace_brigade_with_cache(cache_handle_t *h,
                                                request_rec *r,
                                                apr_bucket_brigade *bb)
@@ -1828,8 +2096,14 @@ static apr_status_t store_body(cache_han
         e = APR_BRIGADE_FIRST(bb);
         a = e->data;
 
-        rv = copy_body(r->pool, a->fd, e->start, dobj->fd, 0, 
-                       dobj->file_size);
+        if(dobj->file_size > conf->minbgsize) {
+            rv = do_bgcopy(a->fd, e->start, dobj->fd, 0, dobj->file_size,
+                           r->connection);
+        }
+        else {
+            rv = copy_body(r->pool, a->fd, e->start, dobj->fd, 0, 
+                           dobj->file_size);
+        }
         if(rv != APR_SUCCESS) {
             ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
                          "disk_cache: Copying body failed, "
@@ -1963,6 +2237,7 @@ static void *create_config(apr_pool_t *p
     conf->maxfs = DEFAULT_MAX_FILE_SIZE;
     conf->minfs = DEFAULT_MIN_FILE_SIZE;
     conf->updtimeout = DEFAULT_UPDATE_TIMEOUT;
+    conf->minbgsize = DEFAULT_MIN_BACKGROUND_SIZE;
 
     conf->cache_root = NULL;
     conf->cache_root_len = 0;
@@ -2065,6 +2340,22 @@ static const char
 }
 
 
+static const char
+*set_cache_minbgsize(cmd_parms *parms, void *in_struct_ptr, const char *arg)
+{
+    disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
+                                                 &disk_cache_module);
+
+    if (apr_strtoff(&conf->minbgsize, arg, NULL, 0) != APR_SUCCESS ||
+            conf->minbgsize < 0)
+    {   
+        return "CacheMinBGSize argument must be a non-negative integer 
representing the min size in bytes for a file to be eligable for background 
caching";
+    }
+
+    return NULL;
+}
+
+
 static const command_rec disk_cache_cmds[] =
 {
     AP_INIT_TAKE1("CacheRoot", set_cache_root, NULL, RSRC_CONF,
@@ -2079,6 +2370,8 @@ static const command_rec disk_cache_cmds
                   "The maximum file size to cache a document"),
     AP_INIT_TAKE1("CacheUpdateTimeout", set_cache_updtimeout, NULL, RSRC_CONF,
                   "Timeout in ms for cache updates"),
+    AP_INIT_TAKE1("CacheMinBGSize", set_cache_minbgsize, NULL, RSRC_CONF,
+                  "The minimum file size for background caching"),
     {NULL}
 };
 
--- mod_disk_cache.h.rwc        2006-10-06 14:22:32.000000000 +0200
+++ mod_disk_cache.h    2006-10-08 19:11:31.000000000 +0200
@@ -99,6 +99,8 @@ typedef struct disk_cache_object {
 #define DEFAULT_MIN_FILE_SIZE 1
 #define DEFAULT_MAX_FILE_SIZE 1000000
 #define DEFAULT_UPDATE_TIMEOUT apr_time_from_sec(10)
+/* Background caching disabled by default */
+#define DEFAULT_MIN_BACKGROUND_SIZE DEFAULT_MAX_FILE_SIZE
 
 typedef struct {
     const char* cache_root;
@@ -108,6 +110,7 @@ typedef struct {
     apr_off_t minfs;             /* minimum file size for cached files */
     apr_off_t maxfs;             /* maximum file size for cached files */
     apr_interval_time_t updtimeout;   /* Cache update timeout */
+    apr_off_t minbgsize;         /* minimum file size to do bg caching */
 } disk_cache_conf;
 
 #define CACHE_ENODATA (APR_OS_START_USERERR+1)
@@ -128,5 +131,31 @@ struct diskcache_bucket_data {
 
 };
 
+/* Stuff needed by the background copy thread */
+typedef struct copyinfo copyinfo;
+struct copyinfo {
+    apr_off_t len;
+    /* Source info */
+    const char *srcfile;
+    apr_finfo_t srcinfo;
+    apr_off_t srcoff;
+    /* Destination info */
+    const char *destfile;
+    apr_off_t destoff; 
+
+    /* Our private pool */
+    apr_pool_t *pool;
+
+#if APR_HAS_THREADS
+    /* Background process info */
+    apr_thread_t *t;
+#endif /* APR_HAS_THREADS */
+#if APR_HAS_FORK
+    apr_proc_t *proc;
+#endif /* APR_HAS_FORK */
+
+    /* For logging */
+    const server_rec *s;
+};
 
 #endif /*MOD_DISK_CACHE_H*/

Reply via email to