fuse: separate queue for FORGET requests
Miklos Szeredi [Tue, 7 Dec 2010 19:16:56 +0000 (20:16 +0100)]
Terje Malmedal reports that a fuse filesystem with 32 million inodes
on a machine with lots of memory can go unresponsive for up to 30
minutes when all those inodes are evicted from the icache.

The reason is that FORGET messages, sent when the inode is evicted,
are queued up together with regular filesystem requests, and while the
huge queue of FORGET messages are processed no other filesystem
operation can proceed.

Since a full fuse request structure is allocated for each inode, these
take up quite a bit of memory as well.

To solve these issues, create a slim 'fuse_forget_link' structure
containing just the minimum of information required to send the FORGET
request and chain these on a separate queue.

When userspace is asking for a request make sure that FORGET and
non-FORGET requests are selected fairly: for each 8 non-FORGET allow
16 FORGET requests.  This will make sure FORGETs do not pile up, yet
other requests are also allowed to proceed while the queued FORGETs
are processed.

Reported-by: Terje Malmedal <terje.malmedal@usit.uio.no>
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>

fs/fuse/dev.c
fs/fuse/dir.c
fs/fuse/fuse_i.h
fs/fuse/inode.c

index 6e07696..fed6530 100644 (file)
@@ -251,6 +251,20 @@ static void queue_request(struct fuse_conn *fc, struct fuse_req *req)
        kill_fasync(&fc->fasync, SIGIO, POLL_IN);
 }
 
+void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget,
+                      u64 nodeid, u64 nlookup)
+{
+       forget->nodeid = nodeid;
+       forget->nlookup = nlookup;
+
+       spin_lock(&fc->lock);
+       fc->forget_list_tail->next = forget;
+       fc->forget_list_tail = forget;
+       wake_up(&fc->waitq);
+       kill_fasync(&fc->fasync, SIGIO, POLL_IN);
+       spin_unlock(&fc->lock);
+}
+
 static void flush_bg_queue(struct fuse_conn *fc)
 {
        while (fc->active_background < fc->max_background &&
@@ -438,12 +452,6 @@ static void fuse_request_send_nowait(struct fuse_conn *fc, struct fuse_req *req)
        }
 }
 
-void fuse_request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
-{
-       req->isreply = 0;
-       fuse_request_send_nowait(fc, req);
-}
-
 void fuse_request_send_background(struct fuse_conn *fc, struct fuse_req *req)
 {
        req->isreply = 1;
@@ -896,9 +904,15 @@ static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs,
        return err;
 }
 
+static int forget_pending(struct fuse_conn *fc)
+{
+       return fc->forget_list_head.next != NULL;
+}
+
 static int request_pending(struct fuse_conn *fc)
 {
-       return !list_empty(&fc->pending) || !list_empty(&fc->interrupts);
+       return !list_empty(&fc->pending) || !list_empty(&fc->interrupts) ||
+               forget_pending(fc);
 }
 
 /* Wait until a request is available on the pending list */
@@ -960,6 +974,50 @@ __releases(fc->lock)
        return err ? err : reqsize;
 }
 
+static struct fuse_forget_link *dequeue_forget(struct fuse_conn *fc)
+{
+       struct fuse_forget_link *forget = fc->forget_list_head.next;
+
+       fc->forget_list_head.next = forget->next;
+       if (fc->forget_list_head.next == NULL)
+               fc->forget_list_tail = &fc->forget_list_head;
+
+       return forget;
+}
+
+static int fuse_read_single_forget(struct fuse_conn *fc,
+                                  struct fuse_copy_state *cs,
+                                  size_t nbytes)
+__releases(fc->lock)
+{
+       int err;
+       struct fuse_forget_link *forget = dequeue_forget(fc);
+       struct fuse_forget_in arg = {
+               .nlookup = forget->nlookup,
+       };
+       struct fuse_in_header ih = {
+               .opcode = FUSE_FORGET,
+               .nodeid = forget->nodeid,
+               .unique = fuse_get_unique(fc),
+               .len = sizeof(ih) + sizeof(arg),
+       };
+
+       spin_unlock(&fc->lock);
+       kfree(forget);
+       if (nbytes < ih.len)
+               return -EINVAL;
+
+       err = fuse_copy_one(cs, &ih, sizeof(ih));
+       if (!err)
+               err = fuse_copy_one(cs, &arg, sizeof(arg));
+       fuse_copy_finish(cs);
+
+       if (err)
+               return err;
+
+       return ih.len;
+}
+
 /*
  * Read a single request into the userspace filesystem's buffer.  This
  * function waits until a request is available, then removes it from
@@ -998,6 +1056,14 @@ static ssize_t fuse_dev_do_read(struct fuse_conn *fc, struct file *file,
                return fuse_read_interrupt(fc, cs, nbytes, req);
        }
 
+       if (forget_pending(fc)) {
+               if (list_empty(&fc->pending) || fc->forget_batch-- > 0)
+                       return fuse_read_single_forget(fc, cs, nbytes);
+
+               if (fc->forget_batch <= -8)
+                       fc->forget_batch = 16;
+       }
+
        req = list_entry(fc->pending.next, struct fuse_req, list);
        req->state = FUSE_REQ_READING;
        list_move(&req->list, &fc->io);
@@ -1090,7 +1156,7 @@ static ssize_t fuse_dev_splice_read(struct file *in, loff_t *ppos,
        if (!fc)
                return -EPERM;
 
-       bufs = kmalloc(pipe->buffers * sizeof (struct pipe_buffer), GFP_KERNEL);
+       bufs = kmalloc(pipe->buffers * sizeof(struct pipe_buffer), GFP_KERNEL);
        if (!bufs)
                return -ENOMEM;
 
@@ -1626,7 +1692,7 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
        if (!fc)
                return -EPERM;
 
-       bufs = kmalloc(pipe->buffers * sizeof (struct pipe_buffer), GFP_KERNEL);
+       bufs = kmalloc(pipe->buffers * sizeof(struct pipe_buffer), GFP_KERNEL);
        if (!bufs)
                return -ENOMEM;
 
@@ -1770,6 +1836,8 @@ __acquires(fc->lock)
        flush_bg_queue(fc);
        end_requests(fc, &fc->pending);
        end_requests(fc, &fc->processing);
+       while (forget_pending(fc))
+               kfree(dequeue_forget(fc));
 }
 
 /*
index c9627c9..6ea42e9 100644 (file)
@@ -10,9 +10,9 @@
 
 #include <linux/pagemap.h>
 #include <linux/file.h>
-#include <linux/gfp.h>
 #include <linux/sched.h>
 #include <linux/namei.h>
+#include <linux/slab.h>
 
 #if BITS_PER_LONG >= 64
 static inline void fuse_dentry_settime(struct dentry *entry, u64 time)
@@ -165,7 +165,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd)
                struct fuse_entry_out outarg;
                struct fuse_conn *fc;
                struct fuse_req *req;
-               struct fuse_req *forget_req;
+               struct fuse_forget_link *forget;
                struct dentry *parent;
                u64 attr_version;
 
@@ -178,8 +178,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd)
                if (IS_ERR(req))
                        return 0;
 
-               forget_req = fuse_get_req(fc);
-               if (IS_ERR(forget_req)) {
+               forget = fuse_alloc_forget();
+               if (!forget) {
                        fuse_put_request(fc, req);
                        return 0;
                }
@@ -199,15 +199,14 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd)
                if (!err) {
                        struct fuse_inode *fi = get_fuse_inode(inode);
                        if (outarg.nodeid != get_node_id(inode)) {
-                               fuse_send_forget(fc, forget_req,
-                                                outarg.nodeid, 1);
+                               fuse_queue_forget(fc, forget, outarg.nodeid, 1);
                                return 0;
                        }
                        spin_lock(&fc->lock);
                        fi->nlookup++;
                        spin_unlock(&fc->lock);
                }
-               fuse_put_request(fc, forget_req);
+               kfree(forget);
                if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
                        return 0;
 
@@ -259,7 +258,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name,
 {
        struct fuse_conn *fc = get_fuse_conn_super(sb);
        struct fuse_req *req;
-       struct fuse_req *forget_req;
+       struct fuse_forget_link *forget;
        u64 attr_version;
        int err;
 
@@ -273,9 +272,9 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name,
        if (IS_ERR(req))
                goto out;
 
-       forget_req = fuse_get_req(fc);
-       err = PTR_ERR(forget_req);
-       if (IS_ERR(forget_req)) {
+       forget = fuse_alloc_forget();
+       err = -ENOMEM;
+       if (!forget) {
                fuse_put_request(fc, req);
                goto out;
        }
@@ -301,13 +300,13 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name,
                           attr_version);
        err = -ENOMEM;
        if (!*inode) {
-               fuse_send_forget(fc, forget_req, outarg->nodeid, 1);
+               fuse_queue_forget(fc, forget, outarg->nodeid, 1);
                goto out;
        }
        err = 0;
 
  out_put_forget:
-       fuse_put_request(fc, forget_req);
+       kfree(forget);
  out:
        return err;
 }
@@ -374,7 +373,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode,
        struct inode *inode;
        struct fuse_conn *fc = get_fuse_conn(dir);
        struct fuse_req *req;
-       struct fuse_req *forget_req;
+       struct fuse_forget_link *forget;
        struct fuse_create_in inarg;
        struct fuse_open_out outopen;
        struct fuse_entry_out outentry;
@@ -388,9 +387,9 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode,
        if (flags & O_DIRECT)
                return -EINVAL;
 
-       forget_req = fuse_get_req(fc);
-       if (IS_ERR(forget_req))
-               return PTR_ERR(forget_req);
+       forget = fuse_alloc_forget();
+       if (!forget)
+               return -ENOMEM;
 
        req = fuse_get_req(fc);
        err = PTR_ERR(req);
@@ -448,10 +447,10 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode,
        if (!inode) {
                flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
                fuse_sync_release(ff, flags);
-               fuse_send_forget(fc, forget_req, outentry.nodeid, 1);
+               fuse_queue_forget(fc, forget, outentry.nodeid, 1);
                return -ENOMEM;
        }
-       fuse_put_request(fc, forget_req);
+       kfree(forget);
        d_instantiate(entry, inode);
        fuse_change_entry_timeout(entry, &outentry);
        fuse_invalidate_attr(dir);
@@ -469,7 +468,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode,
  out_put_request:
        fuse_put_request(fc, req);
  out_put_forget_req:
-       fuse_put_request(fc, forget_req);
+       kfree(forget);
        return err;
 }
 
@@ -483,12 +482,12 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
        struct fuse_entry_out outarg;
        struct inode *inode;
        int err;
-       struct fuse_req *forget_req;
+       struct fuse_forget_link *forget;
 
-       forget_req = fuse_get_req(fc);
-       if (IS_ERR(forget_req)) {
+       forget = fuse_alloc_forget();
+       if (!forget) {
                fuse_put_request(fc, req);
-               return PTR_ERR(forget_req);
+               return -ENOMEM;
        }
 
        memset(&outarg, 0, sizeof(outarg));
@@ -515,10 +514,10 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
        inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
                          &outarg.attr, entry_attr_timeout(&outarg), 0);
        if (!inode) {
-               fuse_send_forget(fc, forget_req, outarg.nodeid, 1);
+               fuse_queue_forget(fc, forget, outarg.nodeid, 1);
                return -ENOMEM;
        }
-       fuse_put_request(fc, forget_req);
+       kfree(forget);
 
        if (S_ISDIR(inode->i_mode)) {
                struct dentry *alias;
@@ -541,7 +540,7 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
        return 0;
 
  out_put_forget_req:
-       fuse_put_request(fc, forget_req);
+       kfree(forget);
        return err;
 }
 
index 57d4a3a..33369c6 100644 (file)
@@ -53,6 +53,13 @@ extern struct mutex fuse_mutex;
 extern unsigned max_user_bgreq;
 extern unsigned max_user_congthresh;
 
+/* One forget request */
+struct fuse_forget_link {
+       u64     nodeid;
+       u64     nlookup;
+       struct fuse_forget_link *next;
+};
+
 /** FUSE inode */
 struct fuse_inode {
        /** Inode data */
@@ -66,7 +73,7 @@ struct fuse_inode {
        u64 nlookup;
 
        /** The request used for sending the FORGET message */
-       struct fuse_req *forget_req;
+       struct fuse_forget_link *forget;
 
        /** Time in jiffies until the file attributes are valid */
        u64 i_time;
@@ -255,7 +262,6 @@ struct fuse_req {
 
        /** Data for asynchronous requests */
        union {
-               struct fuse_forget_in forget_in;
                struct {
                        struct fuse_release_in in;
                        struct path path;
@@ -369,6 +375,13 @@ struct fuse_conn {
        /** Pending interrupts */
        struct list_head interrupts;
 
+       /** Queue of pending forgets */
+       struct fuse_forget_link forget_list_head;
+       struct fuse_forget_link *forget_list_tail;
+
+       /** Batching of FORGET requests (positive indicates FORGET batch) */
+       int forget_batch;
+
        /** Flag indicating if connection is blocked.  This will be
            the case before the INIT reply is received, and if there
            are too many outstading backgrounds requests */
@@ -543,8 +556,10 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct qstr *name,
 /**
  * Send FORGET command
  */
-void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,
-                     u64 nodeid, u64 nlookup);
+void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget,
+                      u64 nodeid, u64 nlookup);
+
+struct fuse_forget_link *fuse_alloc_forget(void);
 
 /**
  * Initialize READ or READDIR request
@@ -656,11 +671,6 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req);
 void fuse_request_send(struct fuse_conn *fc, struct fuse_req *req);
 
 /**
- * Send a request with no reply
- */
-void fuse_request_send_noreply(struct fuse_conn *fc, struct fuse_req *req);
-
-/**
  * Send a request in the background
  */
 void fuse_request_send_background(struct fuse_conn *fc, struct fuse_req *req);
index cfce3ad..7ba4d35 100644 (file)
@@ -71,6 +71,11 @@ struct fuse_mount_data {
        unsigned blksize;
 };
 
+struct fuse_forget_link *fuse_alloc_forget()
+{
+       return kzalloc(sizeof(struct fuse_forget_link), GFP_KERNEL);
+}
+
 static struct inode *fuse_alloc_inode(struct super_block *sb)
 {
        struct inode *inode;
@@ -90,8 +95,8 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
        INIT_LIST_HEAD(&fi->queued_writes);
        INIT_LIST_HEAD(&fi->writepages);
        init_waitqueue_head(&fi->page_waitq);
-       fi->forget_req = fuse_request_alloc();
-       if (!fi->forget_req) {
+       fi->forget = fuse_alloc_forget();
+       if (!fi->forget) {
                kmem_cache_free(fuse_inode_cachep, inode);
                return NULL;
        }
@@ -104,24 +109,10 @@ static void fuse_destroy_inode(struct inode *inode)
        struct fuse_inode *fi = get_fuse_inode(inode);
        BUG_ON(!list_empty(&fi->write_files));
        BUG_ON(!list_empty(&fi->queued_writes));
-       if (fi->forget_req)
-               fuse_request_free(fi->forget_req);
+       kfree(fi->forget);
        kmem_cache_free(fuse_inode_cachep, inode);
 }
 
-void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,
-                     u64 nodeid, u64 nlookup)
-{
-       struct fuse_forget_in *inarg = &req->misc.forget_in;
-       inarg->nlookup = nlookup;
-       req->in.h.opcode = FUSE_FORGET;
-       req->in.h.nodeid = nodeid;
-       req->in.numargs = 1;
-       req->in.args[0].size = sizeof(struct fuse_forget_in);
-       req->in.args[0].value = inarg;
-       fuse_request_send_noreply(fc, req);
-}
-
 static void fuse_evict_inode(struct inode *inode)
 {
        truncate_inode_pages(&inode->i_data, 0);
@@ -129,8 +120,8 @@ static void fuse_evict_inode(struct inode *inode)
        if (inode->i_sb->s_flags & MS_ACTIVE) {
                struct fuse_conn *fc = get_fuse_conn(inode);
                struct fuse_inode *fi = get_fuse_inode(inode);
-               fuse_send_forget(fc, fi->forget_req, fi->nodeid, fi->nlookup);
-               fi->forget_req = NULL;
+               fuse_queue_forget(fc, fi->forget, fi->nodeid, fi->nlookup);
+               fi->forget = NULL;
        }
 }
 
@@ -534,6 +525,7 @@ void fuse_conn_init(struct fuse_conn *fc)
        INIT_LIST_HEAD(&fc->interrupts);
        INIT_LIST_HEAD(&fc->bg_queue);
        INIT_LIST_HEAD(&fc->entry);
+       fc->forget_list_tail = &fc->forget_list_head;
        atomic_set(&fc->num_waiting, 0);
        fc->max_background = FUSE_DEFAULT_MAX_BACKGROUND;
        fc->congestion_threshold = FUSE_DEFAULT_CONGESTION_THRESHOLD;