add "remember" option
authortherealneworld@gmail.com <therealneworld@gmail.com>
Thu, 2 Jun 2011 12:27:02 +0000 (14:27 +0200)
committerMiklos Szeredi <mszeredi@suse.cz>
Thu, 2 Jun 2011 12:27:02 +0000 (14:27 +0200)
This works similar to "noforget" except that eventually the node will
be allowed to expire from the cache.

ChangeLog
include/fuse.h
lib/fuse.c
lib/fuse_i.h
lib/fuse_loop_mt.c
lib/fuse_mt.c
lib/fuse_versionscript

index 572425cc3ce7ddb2f4eb093f4ea85f7b789bba6a..db029ff0ea2e9b0822186108f1638f97074dfc21 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2011-06-02  Miklos Szeredi <miklos@szeredi.hu>
+
+       * Add "remember" option.  This works similar to "noforget" except
+       that eventually the node will be allowed to expire from the cache.
+       Patch by therealneworld@gmail.com
+
 2011-05-27  Miklos Szeredi <miklos@szeredi.hu>
 
        * Check if splice/vmsplice are supported
index b0e6f5b30077b868f94876825b175401dd62d9a0..7e52719379186a2725c3d6721d1815e4e6ea1f50 100644 (file)
@@ -723,6 +723,34 @@ int fuse_is_lib_option(const char *opt);
 int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
                   size_t op_size, void *user_data);
 
+/**
+ * Start the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ * @return 0 on success and -1 on error
+ */
+int fuse_start_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Stop the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ */
+void fuse_stop_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Iterate over cache removing stale entries
+ * use in conjunction with "-oremember"
+ *
+ * NOTE: This is already done for the standard sessions
+ *
+ * @param fuse struct fuse pointer for fuse instance
+ * @return the number of seconds until the next cleanup
+ */
+int fuse_clean_cache(struct fuse *fuse);
+
 /*
  * Stacking API
  */
index b8cce238bfc28c30fcae690e4f8fcad1dea23edb..50f3d0dae0ad7c322921b2f6d796ab8b7ec2432f 100644 (file)
@@ -30,6 +30,7 @@
 #include <signal.h>
 #include <dlfcn.h>
 #include <assert.h>
+#include <poll.h>
 #include <sys/param.h>
 #include <sys/uio.h>
 #include <sys/time.h>
@@ -57,7 +58,7 @@ struct fuse_config {
        double attr_timeout;
        double ac_attr_timeout;
        int ac_attr_timeout_set;
-       int noforget;
+       int remember;
        int nopath;
        int debug;
        int hard_remove;
@@ -128,6 +129,7 @@ struct fuse {
        int pagesize;
        struct list_head partial_slabs;
        struct list_head full_slabs;
+       pthread_t prune_thread;
 };
 
 struct lock {
@@ -151,6 +153,7 @@ struct node {
        int open_count;
        struct timespec stat_updated;
        struct timespec mtime;
+       struct timespec forget_time;
        off_t size;
        struct lock *locks;
        unsigned int is_hidden : 1;
@@ -465,6 +468,10 @@ static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
        return node;
 }
 
+static void curr_time(struct timespec *now);
+static double diff_timespec(const struct timespec *t1,
+                          const struct timespec *t2);
+
 static void free_node(struct fuse *f, struct node *node)
 {
        if (node->name != node->inline_name)
@@ -774,7 +781,7 @@ static struct node *find_node(struct fuse *f, fuse_ino_t parent,
                if (node == NULL)
                        goto out_err;
 
-               if (f->conf.noforget)
+               if (f->conf.remember)
                        node->nlookup = 1;
                node->refctr = 1;
                node->nodeid = next_id(f);
@@ -1170,13 +1177,16 @@ static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
        if (!node->nlookup) {
                unhash_name(f, node);
                unref_node(f, node);
+       } else if (node->nlookup == 1 && f->conf.remember &&
+                  f->conf.remember != -1) {
+               curr_time(&node->forget_time);
        }
        pthread_mutex_unlock(&f->lock);
 }
 
 static void unlink_node(struct fuse *f, struct node *node)
 {
-       if (f->conf.noforget) {
+       if (f->conf.remember) {
                assert(node->nlookup > 1);
                node->nlookup--;
        }
@@ -3832,6 +3842,68 @@ static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
                reply_err(req, err);
 }
 
+static int clean_delay(struct fuse *f)
+{
+       /*
+        * This is calculating the delay between clean runs.  To
+        * reduce the number of cleans we are doing them 10 times
+        * within the remember window.
+        */
+       int min_sleep = 60;
+       int max_sleep = 3600;
+       int sleep_time = f->conf.remember / 10;
+
+       if (sleep_time > max_sleep)
+               return max_sleep;
+       if (sleep_time < min_sleep)
+               return min_sleep;
+       return sleep_time;
+}
+
+int fuse_clean_cache(struct fuse *f)
+{
+       int i;
+       struct node *node, *next;
+       struct timespec now;
+       static int next_clean;
+
+       pthread_mutex_lock(&f->lock);
+       next_clean = clean_delay(f);
+
+       curr_time(&now);
+       for (i = 0; i < f->name_table.size; ++i) {
+               for (node = f->name_table.array[i]; node; node = next) {
+                       double age;
+
+                       next = node->name_next;
+
+                       if (node->nodeid == FUSE_ROOT_ID)
+                               continue;
+
+                       /* Don't forget active directories */
+                       if (node->refctr > 1)
+                               continue;
+
+                       /*
+                        * Only want to try the forget after the lookup count
+                        * has been reduced to 1 and the time to keep the node
+                        * around has expired
+                        */
+                       if (node->nlookup != 1)
+                               continue;
+
+                       age = diff_timespec(&now, &node->forget_time);
+                       if (age > f->conf.remember) {
+                               node->nlookup = 0;
+                               unhash_name(f, node);
+                               unref_node(f, node);
+                       }
+               }
+       }
+       pthread_mutex_unlock(&f->lock);
+       return next_clean;
+}
+
 static struct fuse_lowlevel_ops fuse_path_ops = {
        .init = fuse_lib_init,
        .destroy = fuse_lib_destroy,
@@ -3934,12 +4006,77 @@ struct fuse_cmd *fuse_read_cmd(struct fuse *f)
        return cmd;
 }
 
+static int fuse_session_loop_remember(struct fuse *f)
+{
+       struct fuse_session *se = f->se;
+       int res = 0;
+       struct timespec now;
+       time_t next_clean;
+       struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
+       size_t bufsize = fuse_chan_bufsize(ch);
+       char *buf = (char *) malloc(bufsize);
+       struct pollfd fds = {
+               .fd = fuse_chan_fd(ch),
+               .events = POLLIN
+       };
+
+       if (!buf) {
+               fprintf(stderr, "fuse: failed to allocate read buffer\n");
+               return -1;
+       }
+
+       curr_time(&now);
+       next_clean = now.tv_sec;
+       while (!fuse_session_exited(se)) {
+               struct fuse_chan *tmpch = ch;
+               struct fuse_buf fbuf = {
+                       .mem = buf,
+                       .size = bufsize,
+               };
+               unsigned timeout;
+
+               curr_time(&now);
+               if (now.tv_sec < next_clean)
+                       timeout = next_clean - now.tv_sec;
+               else
+                       timeout = 0;
+
+               res = poll(&fds, 1, timeout * 1000);
+               if (res == -1) {
+                       if (errno == -EINTR)
+                               continue;
+                       else
+                               break;
+               } else if (res > 0) {
+                       res = fuse_session_receive_buf(se, &fbuf, &tmpch);
+
+                       if (res == -EINTR)
+                               continue;
+                       if (res <= 0)
+                               break;
+
+                       fuse_session_process_buf(se, &fbuf, tmpch);
+               } else {
+                       timeout = fuse_clean_cache(f);
+                       curr_time(&now);
+                       next_clean = now.tv_sec + timeout;
+               }
+       }
+
+       free(buf);
+       fuse_session_reset(se);
+       return res < 0 ? -1 : 0;
+}
+
 int fuse_loop(struct fuse *f)
 {
-       if (f)
-               return fuse_session_loop(f->se);
-       else
+       if (!f)
                return -1;
+
+       if (f->conf.remember && f->conf.remember != -1)
+               return fuse_session_loop_remember(f);
+
+       return fuse_session_loop(f->se);
 }
 
 int fuse_invalidate(struct fuse *f, const char *path)
@@ -4019,7 +4156,8 @@ static const struct fuse_opt fuse_lib_opts[] = {
        FUSE_LIB_OPT("ac_attr_timeout=%lf",   ac_attr_timeout, 0),
        FUSE_LIB_OPT("ac_attr_timeout=",      ac_attr_timeout_set, 1),
        FUSE_LIB_OPT("negative_timeout=%lf",  negative_timeout, 0),
-       FUSE_LIB_OPT("noforget",              noforget, 1),
+       FUSE_LIB_OPT("noforget",              remember, -1),
+       FUSE_LIB_OPT("remember=%u",           remember, 0),
        FUSE_LIB_OPT("nopath",                nopath, 1),
        FUSE_LIB_OPT("intr",                  intr, 1),
        FUSE_LIB_OPT("intr_signal=%d",        intr_signal, 0),
@@ -4043,7 +4181,8 @@ static void fuse_lib_help(void)
 "    -o negative_timeout=T  cache timeout for deleted names (0.0s)\n"
 "    -o attr_timeout=T      cache timeout for attributes (1.0s)\n"
 "    -o ac_attr_timeout=T   auto cache timeout for attributes (attr_timeout)\n"
-"    -o noforget            remember inode numbers (increases memory use)\n"
+"    -o noforget            never forget cached inodes\n"
+"    -o remember=T          remember cached inodes for T seconds (0s)\n"
 "    -o intr                allow requests to be interrupted\n"
 "    -o intr_signal=NUM     signal to send on interrupt (%i)\n"
 "    -o modules=M1[:M2...]  names of modules to push onto filesystem stack\n"
@@ -4183,6 +4322,36 @@ static int node_table_init(struct node_table *t)
        return 0;
 }
 
+static void *fuse_prune_nodes(void *fuse)
+{
+       struct fuse *f = fuse;
+       int sleep_time;
+
+       while(1) {
+               sleep_time = fuse_clean_cache(f);
+               sleep(sleep_time);
+       }
+       return NULL;
+}
+
+int fuse_start_cleanup_thread(struct fuse *f)
+{
+       if (f->conf.remember && f->conf.remember != -1)
+               return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
+       return 0;
+}
+
+void fuse_stop_cleanup_thread(struct fuse *f)
+{
+       if (f->conf.remember && f->conf.remember != -1) {
+               pthread_mutex_lock(&f->lock);
+               pthread_cancel(f->prune_thread);
+               pthread_mutex_unlock(&f->lock);
+               pthread_join(f->prune_thread, NULL);
+       }
+}
+
 struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args,
                             const struct fuse_operations *op,
                             size_t op_size, void *user_data, int compat)
index b715da7bd40435642bb04dccf653421c728675f6..dd98737abd84d5c27b5078b04a75e7568c80e906 100644 (file)
@@ -123,3 +123,5 @@ struct fuse *fuse_setup_common(int argc, char *argv[],
                               int compat);
 
 void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
index ab5fd11369626ca8cb2eb8444568026098f63cee..b5ad1c74a3df9267e35a80cf74c4a95a757fdc85 100644 (file)
@@ -9,6 +9,7 @@
 #include "fuse_lowlevel.h"
 #include "fuse_misc.h"
 #include "fuse_kernel.h"
+#include "fuse_i.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -60,7 +61,7 @@ static void list_del_worker(struct fuse_worker *w)
        next->prev = prev;
 }
 
-static int fuse_start_thread(struct fuse_mt *mt);
+static int fuse_loop_start_thread(struct fuse_mt *mt);
 
 static void *fuse_do_work(void *data)
 {
@@ -110,7 +111,7 @@ static void *fuse_do_work(void *data)
                if (!isforget)
                        mt->numavail--;
                if (mt->numavail == 0)
-                       fuse_start_thread(mt);
+                       fuse_loop_start_thread(mt);
                pthread_mutex_unlock(&mt->lock);
 
                fuse_session_process_buf(mt->se, &fbuf, ch);
@@ -141,27 +142,13 @@ static void *fuse_do_work(void *data)
        return NULL;
 }
 
-static int fuse_start_thread(struct fuse_mt *mt)
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
 {
        sigset_t oldset;
        sigset_t newset;
        int res;
        pthread_attr_t attr;
        char *stack_size;
-       struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
-       if (!w) {
-               fprintf(stderr, "fuse: failed to allocate worker structure\n");
-               return -1;
-       }
-       memset(w, 0, sizeof(struct fuse_worker));
-       w->bufsize = fuse_chan_bufsize(mt->prevch);
-       w->buf = malloc(w->bufsize);
-       w->mt = mt;
-       if (!w->buf) {
-               fprintf(stderr, "fuse: failed to allocate read buffer\n");
-               free(w);
-               return -1;
-       }
 
        /* Override default stack size */
        pthread_attr_init(&attr);
@@ -176,12 +163,38 @@ static int fuse_start_thread(struct fuse_mt *mt)
        sigaddset(&newset, SIGHUP);
        sigaddset(&newset, SIGQUIT);
        pthread_sigmask(SIG_BLOCK, &newset, &oldset);
-       res = pthread_create(&w->thread_id, &attr, fuse_do_work, w);
+       res = pthread_create(thread_id, &attr, func, arg);
        pthread_sigmask(SIG_SETMASK, &oldset, NULL);
        pthread_attr_destroy(&attr);
        if (res != 0) {
                fprintf(stderr, "fuse: error creating thread: %s\n",
                        strerror(res));
+               return -1;
+       }
+
+       return 0;
+}
+
+static int fuse_loop_start_thread(struct fuse_mt *mt)
+{
+       int res;
+       struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+       if (!w) {
+               fprintf(stderr, "fuse: failed to allocate worker structure\n");
+               return -1;
+       }
+       memset(w, 0, sizeof(struct fuse_worker));
+       w->bufsize = fuse_chan_bufsize(mt->prevch);
+       w->buf = malloc(w->bufsize);
+       w->mt = mt;
+       if (!w->buf) {
+               fprintf(stderr, "fuse: failed to allocate read buffer\n");
+               free(w);
+               return -1;
+       }
+
+       res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+       if (res == -1) {
                free(w->buf);
                free(w);
                return -1;
@@ -221,7 +234,7 @@ int fuse_session_loop_mt(struct fuse_session *se)
        fuse_mutex_init(&mt.lock);
 
        pthread_mutex_lock(&mt.lock);
-       err = fuse_start_thread(&mt);
+       err = fuse_loop_start_thread(&mt);
        pthread_mutex_unlock(&mt.lock);
        if (!err) {
                /* sem_wait() is interruptible */
index 95c3a5cb681b7a0167b5e8a3e8acfd820655986d..f6dbe71b2137a78f2eebbd9df370fe1a74b939f9 100644 (file)
@@ -110,7 +110,13 @@ int fuse_loop_mt(struct fuse *f)
        if (f == NULL)
                return -1;
 
-       return fuse_session_loop_mt(fuse_get_session(f));
+       int res = fuse_start_cleanup_thread(f);
+       if (res)
+               return -1;
+
+       res = fuse_session_loop_mt(fuse_get_session(f));
+       fuse_stop_cleanup_thread(f);
+       return res;
 }
 
 FUSE_SYMVER(".symver fuse_loop_mt_proc,__fuse_loop_mt@");
index 46945759c75edc9d40f9a05ca8ef2b4f95b950d1..96403c5f4e8b4d6cef697b48723ec6ddba1262e5 100644 (file)
@@ -191,6 +191,9 @@ FUSE_2.9 {
                fuse_reply_data;
                fuse_session_process_buf;
                fuse_session_receive_buf;
+               fuse_start_cleanup_thread;
+               fuse_stop_cleanup_thread;
+               fuse_clean_cache;
 
        local:
                *;