NFSD: handle GETATTR conflict with write delegation
authorDai Ngo <dai.ngo@oracle.com>
Fri, 30 Jun 2023 01:52:39 +0000 (18:52 -0700)
committerChuck Lever <chuck.lever@oracle.com>
Tue, 29 Aug 2023 21:45:22 +0000 (17:45 -0400)
If the GETATTR request on a file that has write delegation in effect and
the request attributes include the change info and size attribute then
the write delegation is recalled. If the delegation is returned within
30ms then the GETATTR is serviced as normal otherwise the NFS4ERR_DELAY
error is returned for the GETATTR.

Add counter for write delegation recall due to conflict GETATTR. This is
used to evaluate the need to implement CB_GETATTR to adoid recalling the
delegation with conflit GETATTR.

Signed-off-by: Dai Ngo <dai.ngo@oracle.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
fs/nfsd/nfs4state.c
fs/nfsd/nfs4xdr.c
fs/nfsd/state.h
fs/nfsd/stats.c
fs/nfsd/stats.h

index daf305daa7516afad23a7ade7aa8e3d5f8b16f20..b56ea72d43501db1135a2dda9122f76b717168a5 100644 (file)
@@ -8341,3 +8341,68 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
 {
        get_stateid(cstate, &u->write.wr_stateid);
 }
+
+/**
+ * nfsd4_deleg_getattr_conflict - Recall if GETATTR causes conflict
+ * @rqstp: RPC transaction context
+ * @inode: file to be checked for a conflict
+ *
+ * This function is called when there is a conflict between a write
+ * delegation and a change/size GETATTR from another client. The server
+ * must either use the CB_GETATTR to get the current values of the
+ * attributes from the client that holds the delegation or recall the
+ * delegation before replying to the GETATTR. See RFC 8881 section
+ * 18.7.4.
+ *
+ * The current implementation does not support CB_GETATTR yet. However
+ * this can avoid recalling the delegation could be added in follow up
+ * work.
+ *
+ * Returns 0 if there is no conflict; otherwise an nfs_stat
+ * code is returned.
+ */
+__be32
+nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode)
+{
+       __be32 status;
+       struct file_lock_context *ctx;
+       struct file_lock *fl;
+       struct nfs4_delegation *dp;
+
+       ctx = locks_inode_context(inode);
+       if (!ctx)
+               return 0;
+       spin_lock(&ctx->flc_lock);
+       list_for_each_entry(fl, &ctx->flc_lease, fl_list) {
+               if (fl->fl_flags == FL_LAYOUT)
+                       continue;
+               if (fl->fl_lmops != &nfsd_lease_mng_ops) {
+                       /*
+                        * non-nfs lease, if it's a lease with F_RDLCK then
+                        * we are done; there isn't any write delegation
+                        * on this inode
+                        */
+                       if (fl->fl_type == F_RDLCK)
+                               break;
+                       goto break_lease;
+               }
+               if (fl->fl_type == F_WRLCK) {
+                       dp = fl->fl_owner;
+                       if (dp->dl_recall.cb_clp == *(rqstp->rq_lease_breaker)) {
+                               spin_unlock(&ctx->flc_lock);
+                               return 0;
+                       }
+break_lease:
+                       spin_unlock(&ctx->flc_lock);
+                       nfsd_stats_wdeleg_getattr_inc();
+                       status = nfserrno(nfsd_open_break_lease(inode, NFSD_MAY_READ));
+                       if (status != nfserr_jukebox ||
+                                       !nfsd_wait_for_delegreturn(rqstp, inode))
+                               return status;
+                       return 0;
+               }
+               break;
+       }
+       spin_unlock(&ctx->flc_lock);
+       return 0;
+}
index b30dca7de8cc09c59e0d4cc3c01f42aff6341fca..95aafe52fa0c65b25b354148471020ade9f456a6 100644 (file)
@@ -2984,6 +2984,11 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
                if (status)
                        goto out;
        }
+       if (bmval0 & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
+               status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry));
+               if (status)
+                       goto out;
+       }
 
        err = vfs_getattr(&path, &stat,
                          STATX_BASIC_STATS | STATX_BTIME | STATX_CHANGE_COOKIE,
index d49d3060ed4f7cccd8e9607965c71a63aee9b992..cbddcf484dbac76ea55115ba18d734dbfaf99cf6 100644 (file)
@@ -732,4 +732,7 @@ static inline bool try_to_expire_client(struct nfs4_client *clp)
        cmpxchg(&clp->cl_state, NFSD4_COURTESY, NFSD4_EXPIRABLE);
        return clp->cl_state == NFSD4_EXPIRABLE;
 }
+
+extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
+                               struct inode *inode);
 #endif   /* NFSD4_STATE_H */
index 777e24e5da33bd456c4ff432c74f0cb0e2c36c6a..63797635e1c328d4b988ae558111ea6e9bfdbee9 100644 (file)
@@ -65,6 +65,8 @@ static int nfsd_show(struct seq_file *seq, void *v)
                seq_printf(seq, " %lld",
                           percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_NFS4_OP(i)]));
        }
+       seq_printf(seq, "\nwdeleg_getattr %lld",
+               percpu_counter_sum_positive(&nfsdstats.counter[NFSD_STATS_WDELEG_GETATTR]));
 
        seq_putc(seq, '\n');
 #endif
index 9b43dc3d999139c765d41d935953ae6ac42d39d3..cf5524e7ca06237d9d1396721d2476c34d9126f4 100644 (file)
@@ -22,6 +22,7 @@ enum {
        NFSD_STATS_FIRST_NFS4_OP,       /* count of individual nfsv4 operations */
        NFSD_STATS_LAST_NFS4_OP = NFSD_STATS_FIRST_NFS4_OP + LAST_NFS4_OP,
 #define NFSD_STATS_NFS4_OP(op) (NFSD_STATS_FIRST_NFS4_OP + (op))
+       NFSD_STATS_WDELEG_GETATTR,      /* count of getattr conflict with wdeleg */
 #endif
        NFSD_STATS_COUNTERS_NUM
 };
@@ -93,4 +94,10 @@ static inline void nfsd_stats_drc_mem_usage_sub(struct nfsd_net *nn, s64 amount)
        percpu_counter_sub(&nn->counter[NFSD_NET_DRC_MEM_USAGE], amount);
 }
 
+#ifdef CONFIG_NFSD_V4
+static inline void nfsd_stats_wdeleg_getattr_inc(void)
+{
+       percpu_counter_inc(&nfsdstats.counter[NFSD_STATS_WDELEG_GETATTR]);
+}
+#endif
 #endif /* _NFSD_STATS_H */