NFSv4: Respect the server imposed limit on how many changes we may cache
authorTrond Myklebust <trond.myklebust@primarydata.com>
Sat, 5 Sep 2015 23:06:58 +0000 (19:06 -0400)
committerTrond Myklebust <trond.myklebust@primarydata.com>
Mon, 7 Sep 2015 16:36:17 +0000 (12:36 -0400)
The NFSv4 delegation spec allows the server to tell a client to limit how
much data it cache after the file is closed. In return, the server
guarantees enough free space to avoid ENOSPC situations, etc.
Prior to this patch, we assumed we could always cache aggressively after
close. Unfortunately, this causes problems with servers that set the
limit to 0 and therefore do not offer any ENOSPC guarantees.

Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
fs/nfs/delegation.c
fs/nfs/delegation.h
fs/nfs/file.c
fs/nfs/internal.h
fs/nfs/nfs4file.c

index cd503cc2251cf2955aa0f7d5034a4334d2d3fbaa..2714ef835bdd4261cbc301838c050f42896c24c0 100644 (file)
@@ -900,3 +900,28 @@ bool nfs4_copy_delegation_stateid(nfs4_stateid *dst, struct inode *inode,
        rcu_read_unlock();
        return ret;
 }
+
+/**
+ * nfs4_delegation_flush_on_close - Check if we must flush file on close
+ * @inode: inode to check
+ *
+ * This function checks the number of outstanding writes to the file
+ * against the delegation 'space_limit' field to see if
+ * the spec requires us to flush the file on close.
+ */
+bool nfs4_delegation_flush_on_close(const struct inode *inode)
+{
+       struct nfs_inode *nfsi = NFS_I(inode);
+       struct nfs_delegation *delegation;
+       bool ret = true;
+
+       rcu_read_lock();
+       delegation = rcu_dereference(nfsi->delegation);
+       if (delegation == NULL || !(delegation->type & FMODE_WRITE))
+               goto out;
+       if (nfsi->nrequests < delegation->pagemod_limit)
+               ret = false;
+out:
+       rcu_read_unlock();
+       return ret;
+}
index 554178a17376616be9b1bebed792e7afcea2e748..a44829173e573d1ceca061a9f23b76752f7bb30e 100644 (file)
@@ -61,6 +61,7 @@ bool nfs4_copy_delegation_stateid(nfs4_stateid *dst, struct inode *inode, fmode_
 void nfs_mark_delegation_referenced(struct nfs_delegation *delegation);
 int nfs4_have_delegation(struct inode *inode, fmode_t flags);
 int nfs4_check_delegation(struct inode *inode, fmode_t flags);
+bool nfs4_delegation_flush_on_close(const struct inode *inode);
 
 #endif
 
index 526a2681d9750380c5ef8e8cc26698e381ca6e6d..c0f9b1ed12b9eb281909926f2befacd13db1a026 100644 (file)
@@ -142,7 +142,7 @@ EXPORT_SYMBOL_GPL(nfs_file_llseek);
 /*
  * Flush all dirty pages, and check for write errors.
  */
-int
+static int
 nfs_file_flush(struct file *file, fl_owner_t id)
 {
        struct inode    *inode = file_inode(file);
@@ -153,17 +153,9 @@ nfs_file_flush(struct file *file, fl_owner_t id)
        if ((file->f_mode & FMODE_WRITE) == 0)
                return 0;
 
-       /*
-        * If we're holding a write delegation, then just start the i/o
-        * but don't wait for completion (or send a commit).
-        */
-       if (NFS_PROTO(inode)->have_delegation(inode, FMODE_WRITE))
-               return filemap_fdatawrite(file->f_mapping);
-
        /* Flush writes to the server and return any errors */
        return vfs_fsync(file, 0);
 }
-EXPORT_SYMBOL_GPL(nfs_file_flush);
 
 ssize_t
 nfs_file_read(struct kiocb *iocb, struct iov_iter *to)
index 9ab3b1c21bb41f3e9041d776bdd7737a28648993..56cfde26fb9cea0100a99bb7c3fe8a2be813dc63 100644 (file)
@@ -360,7 +360,6 @@ int nfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *)
 /* file.c */
 int nfs_file_fsync_commit(struct file *, loff_t, loff_t, int);
 loff_t nfs_file_llseek(struct file *, loff_t, int);
-int nfs_file_flush(struct file *, fl_owner_t);
 ssize_t nfs_file_read(struct kiocb *, struct iov_iter *);
 ssize_t nfs_file_splice_read(struct file *, loff_t *, struct pipe_inode_info *,
                             size_t, unsigned int);
index 43f1590b9240d57f3fd80ca120946344375dd1a3..b0dbe0abed53631a22e938b332b675b501b9e978 100644 (file)
@@ -6,7 +6,9 @@
 #include <linux/fs.h>
 #include <linux/falloc.h>
 #include <linux/nfs_fs.h>
+#include "delegation.h"
 #include "internal.h"
+#include "iostat.h"
 #include "fscache.h"
 #include "pnfs.h"
 
@@ -99,6 +101,31 @@ out_drop:
        goto out_put_ctx;
 }
 
+/*
+ * Flush all dirty pages, and check for write errors.
+ */
+static int
+nfs4_file_flush(struct file *file, fl_owner_t id)
+{
+       struct inode    *inode = file_inode(file);
+
+       dprintk("NFS: flush(%pD2)\n", file);
+
+       nfs_inc_stats(inode, NFSIOS_VFSFLUSH);
+       if ((file->f_mode & FMODE_WRITE) == 0)
+               return 0;
+
+       /*
+        * If we're holding a write delegation, then check if we're required
+        * to flush the i/o on close. If not, then just start the i/o now.
+        */
+       if (!nfs4_delegation_flush_on_close(inode))
+               return filemap_fdatawrite(file->f_mapping);
+
+       /* Flush writes to the server and return any errors */
+       return vfs_fsync(file, 0);
+}
+
 static int
 nfs4_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 {
@@ -177,7 +204,7 @@ const struct file_operations nfs4_file_operations = {
        .write_iter     = nfs_file_write,
        .mmap           = nfs_file_mmap,
        .open           = nfs4_file_open,
-       .flush          = nfs_file_flush,
+       .flush          = nfs4_file_flush,
        .release        = nfs_file_release,
        .fsync          = nfs4_file_fsync,
        .lock           = nfs_lock,