Skip to contentSkip to conversation

NFSD support for asynchronous COPY

PATCH Superseded Olga Kornievskaia 5 participants 36 messages Sep 28, 2017 – Oct 12, 2017 1 message not in the archive

Subsystems (2)

v4 of 10 Superseded Latest is v10 →

lore
Share & tools
lore
#0 lore raw

v4 cover NFSD support for asynchronous COPY

To do asynchronous copies, NFSD creates a single threaded workqueue and does not tie up an NFSD thread to complete the copy. Upon receiving the COPY, it generates a unique copy stateid (stores a global list for keeping track of state for OFFLOAD_STATUS to be queried by), queues up a workqueue for the copy, and replies back to the client. nfsd4_copy arguments that are allocated on the stack are copied for the work item.

In the async copy handler, it calls into VFS copy_file_range() with 4MB chunks and loops until it completes the requested copy size. If error is encountered it's saved but also we save the amount of data copied so far. Once done, the results are queued for the callback workqueue and sent via CB_OFFLOAD. Also currently, choosing to clean up the copy state information stored in the global list when cope is done and not doing it when callback's release function (it could be done there alternatively if needed it?).

When the source server received an OFFLOAD_CANCEL, it will remove the stateid from the global list and mark the copy cancelled. If its cancelled we are choosing not to send the CB_OFFLOAD back to the client.

Olga Kornievskaia (10):
NFSD CB_OFFLOAD xdr
NFSD OFFLOAD_STATUS xdr
NFSD OFFLOAD_CANCEL xdr
NFSD xdr callback stateid in async COPY reply NFSD first draft of async copy
NFSD return nfs4_stid in nfs4_preprocess_stateid_op NFSD create new stateid for async copy
NFSD handle OFFLOAD_CANCEL op
NFSD support OFFLOAD_STATUS
NFSD stop queued async copies on client shutdown

 fs/nfsd/netns.h        |   8 ++
 fs/nfsd/nfs4callback.c |  97 +++++++++++++++++
 fs/nfsd/nfs4proc.c     | 276 ++++++++++++++++++++++++++++++++++++++++++++-----
 fs/nfsd/nfs4state.c    | 100 +++++++++++++++++-
 fs/nfsd/nfs4xdr.c      |  53 ++++++++--
 fs/nfsd/nfsctl.c       |   1 +
 fs/nfsd/state.h        |  27 ++++-
 fs/nfsd/xdr4.h         |  25 +++++
 fs/nfsd/xdr4cb.h       |  10 ++
 9 files changed, 559 insertions(+), 38 deletions(-)
Signature · 1 line
1.8.3.1
#1 lore raw

PATCH v4 1/10 NFSD CB_OFFLOAD xdr

---
 fs/nfsd/nfs4callback.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfsd/state.h        |  1 +
 fs/nfsd/xdr4.h         |  6 ++++
 fs/nfsd/xdr4cb.h       | 10 ++++++
 4 files changed, 114 insertions(+)
fs/nfsd/nfs4callback.c +97 -0
index 49b0a9e..d12d914 100644
@@ -39,6 +39,7 @@
 #include "state.h"
 #include "netns.h"
 #include "xdr4cb.h"
+#include "xdr4.h"
 
 #define NFSDDBG_FACILITY                NFSDDBG_PROC
 
@@ -105,6 +106,7 @@ enum nfs_cb_opnum4 {
 	OP_CB_WANTS_CANCELLED		= 12,
 	OP_CB_NOTIFY_LOCK		= 13,
 	OP_CB_NOTIFY_DEVICEID		= 14,
+	OP_CB_OFFLOAD			= 15,
 	OP_CB_ILLEGAL			= 10044
 };
 
@@ -683,6 +685,100 @@ static int nfs4_xdr_dec_cb_notify_lock(struct rpc_rqst *rqstp,
 }
 
 /*
+ * struct write_response4 {
+ *	stateid4	wr_callback_id<1>;
+ *	length4		wr_count;
+ *	stable_how4	wr_committed;
+ *	verifier4	wr_writeverf;
+ * };
+ * union offload_info4 switch (nfsstat4 coa_status) {
+ *	case NFS4_OK:
+ *		write_response4	coa_resok4;
+ *	default:
+ *	length4		coa_bytes_copied;
+ * };
+ * struct CB_OFFLOAD4args {
+ *	nfs_fh4		coa_fh;
+ *	stateid4	coa_stateid;
+ *	offload_info4	coa_offload_info;
+ * };
+ */
+static void encode_offload_info4(struct xdr_stream *xdr,
+				 __be32 nfserr,
+				 const struct nfsd4_copy *cp)
+{
+	__be32 *p;
+
+	p = xdr_reserve_space(xdr, 4);
+	*p++ = nfserr;
+	if (!nfserr) {
+		p = xdr_reserve_space(xdr, 4 + 8 + 4 + NFS4_VERIFIER_SIZE);
+		p = xdr_encode_empty_array(p);
+		p = xdr_encode_hyper(p, cp->cp_res.wr_bytes_written);
+		*p++ = cpu_to_be32(cp->cp_res.wr_stable_how);
+		p = xdr_encode_opaque_fixed(p, cp->cp_res.wr_verifier.data,
+					    NFS4_VERIFIER_SIZE);
+	} else {
+		p = xdr_reserve_space(xdr, 8);
+		p = xdr_encode_hyper(p, cp->cp_res.wr_bytes_written);
+	}
+}
+
+static void encode_cb_offload4args(struct xdr_stream *xdr,
+				   __be32 nfserr,
+				   const struct knfsd_fh *fh,
+				   const struct nfsd4_copy *cp,
+				   struct nfs4_cb_compound_hdr *hdr)
+{
+	__be32 *p;
+
+	p = xdr_reserve_space(xdr, 4);
+	*p++ = cpu_to_be32(OP_CB_OFFLOAD);
+	encode_nfs_fh4(xdr, fh);
+	encode_stateid4(xdr, &cp->cp_res.cb_stateid);
+	encode_offload_info4(xdr, nfserr, cp);
+
+	hdr->nops++;
+}
+
+static void nfs4_xdr_enc_cb_offload(struct rpc_rqst *req,
+				    struct xdr_stream *xdr,
+				    const void *data)
+{
+	const struct nfsd4_callback *cb = data;
+	const struct nfsd4_copy *cp =
+		container_of(cb, struct nfsd4_copy, cp_cb);
+	struct nfs4_cb_compound_hdr hdr = {
+		.ident = 0,
+		.minorversion = cb->cb_clp->cl_minorversion,
+	};
+
+	encode_cb_compound4args(xdr, &hdr);
+	encode_cb_sequence4args(xdr, cb, &hdr);
+	encode_cb_offload4args(xdr, cp->nfserr, &cp->fh, cp, &hdr);
+	encode_cb_nops(&hdr);
+}
+
+static int nfs4_xdr_dec_cb_offload(struct rpc_rqst *rqstp,
+				   struct xdr_stream *xdr,
+				   void *data)
+{
+	struct nfsd4_callback *cb = data;
+	struct nfs4_cb_compound_hdr hdr;
+	int status;
+
+	status = decode_cb_compound4res(xdr, &hdr);
+	if (unlikely(status))
+		return status;
+
+	if (cb) {
+		status = decode_cb_sequence4res(xdr, cb);
+		if (unlikely(status || cb->cb_seq_status))
+			return status;
+	}
+	return decode_cb_op_status(xdr, OP_CB_OFFLOAD, &cb->cb_status);
+}
+/*
  * RPC procedure tables
  */
 #define PROC(proc, call, argtype, restype)				\
@@ -703,6 +799,7 @@ static int nfs4_xdr_dec_cb_notify_lock(struct rpc_rqst *rqstp,
 	PROC(CB_LAYOUT,	COMPOUND,	cb_layout,	cb_layout),
 #endif
 	PROC(CB_NOTIFY_LOCK,	COMPOUND,	cb_notify_lock,	cb_notify_lock),
+	PROC(CB_OFFLOAD,	COMPOUND,	cb_offload,	cb_offload),
 };
 
 static unsigned int nfs4_cb_counts[ARRAY_SIZE(nfs4_cb_procedures)];
fs/nfsd/state.h +1 -0
index 005c911..f8b0210 100644
@@ -570,6 +570,7 @@ enum nfsd4_cb_op {
 	NFSPROC4_CLNT_CB_NULL = 0,
 	NFSPROC4_CLNT_CB_RECALL,
 	NFSPROC4_CLNT_CB_LAYOUT,
+	NFSPROC4_CLNT_CB_OFFLOAD,
 	NFSPROC4_CLNT_CB_SEQUENCE,
 	NFSPROC4_CLNT_CB_NOTIFY_LOCK,
 };
fs/nfsd/xdr4.h +6 -0
index 1e4edbf..4ac2676 100644
@@ -507,6 +507,7 @@ struct nfsd42_write_res {
 	u64			wr_bytes_written;
 	u32			wr_stable_how;
 	nfs4_verifier		wr_verifier;
+	stateid_t		cb_stateid;
 };
 
 struct nfsd4_copy {
@@ -523,6 +524,11 @@ struct nfsd4_copy {
 
 	/* response */
 	struct nfsd42_write_res	cp_res;
+
+	/* for cb_offload */
+	struct nfsd4_callback	cp_cb;
+	__be32			nfserr;
+	struct knfsd_fh		fh;
 };
 
 struct nfsd4_seek {
fs/nfsd/xdr4cb.h +10 -0
index 49b719d..7e39913 100644
@@ -37,3 +37,13 @@
 #define NFS4_dec_cb_notify_lock_sz	(cb_compound_dec_hdr_sz  +      \
 					cb_sequence_dec_sz +            \
 					op_dec_sz)
+#define enc_cb_offload_info_sz		(1 + 1 + 2 + 1 +		\
+					XDR_QUADLEN(NFS4_VERIFIER_SIZE))
+#define NFS4_enc_cb_offload_sz		(cb_compound_enc_hdr_sz +       \
+					cb_sequence_enc_sz +            \
+					enc_nfs4_fh_sz +		\
+					enc_stateid_sz +		\
+					enc_cb_offload_info_sz)
+#define NFS4_dec_cb_offload_sz		(cb_compound_dec_hdr_sz  +      \
+					cb_sequence_dec_sz +            \
+					op_dec_sz)
Signature · 1 line
1.8.3.1
#2 lore raw

PATCH v4 2/10 NFSD OFFLOAD_STATUS xdr

In reply to Olga Kornievskaia
---
 fs/nfsd/nfs4proc.c | 20 ++++++++++++++++++++
 fs/nfsd/nfs4xdr.c  | 30 ++++++++++++++++++++++++++++--
 fs/nfsd/xdr4.h     | 10 ++++++++++
 3 files changed, 58 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +20 -0
index 3c69db7..8601fc4 100644
@@ -1142,6 +1142,13 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 	fput(file);
 	return status;
 }
+static __be32
+nfsd4_offload_status(struct svc_rqst *rqstp,
+		     struct nfsd4_compound_state *cstate,
+		     union nfsd4_op_u *u)
+{
+	return nfserr_notsupp;
+}
 
 static __be32
 nfsd4_allocate(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
@@ -2039,6 +2046,14 @@ static inline u32 nfsd4_copy_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 		1 /* cr_synchronous */) * sizeof(__be32);
 }
 
+static inline u32 nfsd4_offload_status_rsize(struct svc_rqst *rqstp,
+					     struct nfsd4_op *op)
+{
+	return (op_encode_hdr_size +
+		2 /* osr_count */ +
+		1 /* osr_complete<1> optional 0 for now */) * sizeof(__be32);
+}
+
 #ifdef CONFIG_NFSD_PNFS
 static inline u32 nfsd4_getdeviceinfo_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 {
@@ -2452,6 +2467,11 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 		.op_name = "OP_SEEK",
 		.op_rsize_bop = nfsd4_seek_rsize,
 	},
+	[OP_OFFLOAD_STATUS] = {
+		.op_func = nfsd4_offload_status,
+		.op_name = "OP_OFFLOAD_STATUS",
+		.op_rsize_bop = nfsd4_offload_status_rsize,
+	},
 };
 
 /**
fs/nfsd/nfs4xdr.c +28 -2
index 2c61c6b..ed8b61f 100644
@@ -1768,6 +1768,13 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
 }
 
 static __be32
+nfsd4_decode_offload_status(struct nfsd4_compoundargs *argp,
+			    struct nfsd4_offload_status *os)
+{
+	return nfsd4_decode_stateid(argp, &os->stateid);
+}
+
+static __be32
 nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek)
 {
 	DECODE_HEAD;
@@ -1874,7 +1881,7 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
 	[OP_LAYOUTERROR]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_LAYOUTSTATS]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_OFFLOAD_CANCEL]	= (nfsd4_dec)nfsd4_decode_notsupp,
-	[OP_OFFLOAD_STATUS]	= (nfsd4_dec)nfsd4_decode_notsupp,
+	[OP_OFFLOAD_STATUS]	= (nfsd4_dec)nfsd4_decode_offload_status,
 	[OP_READ_PLUS]		= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_SEEK]		= (nfsd4_dec)nfsd4_decode_seek,
 	[OP_WRITE_SAME]		= (nfsd4_dec)nfsd4_decode_notsupp,
@@ -4216,6 +4223,25 @@ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
 }
 
 static __be32
+nfsd4_encode_offload_status(struct nfsd4_compoundres *resp, __be32 nfserr,
+			    struct nfsd4_offload_status *os)
+{
+	struct xdr_stream *xdr = &resp->xdr;
+	__be32 *p;
+
+	if (nfserr)
+		return nfserr;
+
+	p = xdr_reserve_space(xdr, 8 + 4);
+	if (!p)
+		return nfserr_resource;
+	p = xdr_encode_hyper(p, os->count);
+	*p++ = cpu_to_be32(0);
+
+	return nfserr;
+}
+
+static __be32
 nfsd4_encode_seek(struct nfsd4_compoundres *resp, __be32 nfserr,
 		  struct nfsd4_seek *seek)
 {
@@ -4318,7 +4344,7 @@ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
 	[OP_LAYOUTERROR]	= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_LAYOUTSTATS]	= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_OFFLOAD_CANCEL]	= (nfsd4_enc)nfsd4_encode_noop,
-	[OP_OFFLOAD_STATUS]	= (nfsd4_enc)nfsd4_encode_noop,
+	[OP_OFFLOAD_STATUS]	= (nfsd4_enc)nfsd4_encode_offload_status,
 	[OP_READ_PLUS]		= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_SEEK]		= (nfsd4_enc)nfsd4_encode_seek,
 	[OP_WRITE_SAME]		= (nfsd4_enc)nfsd4_encode_noop,
fs/nfsd/xdr4.h +10 -0
index 4ac2676..9b0c099 100644
@@ -542,6 +542,15 @@ struct nfsd4_seek {
 	loff_t		seek_pos;
 };
 
+struct nfsd4_offload_status {
+	/* request */
+	stateid_t	stateid;
+
+	/* response */
+	u64		count;
+	u32		status;
+};
+
 struct nfsd4_op {
 	int					opnum;
 	const struct nfsd4_operation *		opdesc;
@@ -600,6 +609,7 @@ struct nfsd4_op {
 		struct nfsd4_fallocate		deallocate;
 		struct nfsd4_clone		clone;
 		struct nfsd4_copy		copy;
+		struct nfsd4_offload_status	offload_status;
 		struct nfsd4_seek		seek;
 	} u;
 	struct nfs4_replay *			replay;
Signature · 1 line
1.8.3.1
#3 lore raw

Re: [PATCH v4 02/10] NFSD OFFLOAD_STATUS xdr

Quoted 81 lines · Olga Kornievskaia
---
 fs/nfsd/nfs4proc.c | 20 ++++++++++++++++++++
 fs/nfsd/nfs4xdr.c  | 30 ++++++++++++++++++++++++++++--
 fs/nfsd/xdr4.h     | 10 ++++++++++
 3 files changed, 58 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +20 -0
index 3c69db7..8601fc4 100644
@@ -1142,6 +1142,13 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 	fput(file);
 	return status;
 }
+static __be32
+nfsd4_offload_status(struct svc_rqst *rqstp,
+		     struct nfsd4_compound_state *cstate,
+		     union nfsd4_op_u *u)
+{
+	return nfserr_notsupp;
+}
 
 static __be32
 nfsd4_allocate(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
@@ -2039,6 +2046,14 @@ static inline u32 nfsd4_copy_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 		1 /* cr_synchronous */) * sizeof(__be32);
 }
 
+static inline u32 nfsd4_offload_status_rsize(struct svc_rqst *rqstp,
+					     struct nfsd4_op *op)
+{
+	return (op_encode_hdr_size +
+		2 /* osr_count */ +
+		1 /* osr_complete<1> optional 0 for now */) * sizeof(__be32);
+}
+
 #ifdef CONFIG_NFSD_PNFS
 static inline u32 nfsd4_getdeviceinfo_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 {
@@ -2452,6 +2467,11 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 		.op_name = "OP_SEEK",
 		.op_rsize_bop = nfsd4_seek_rsize,
 	},
+	[OP_OFFLOAD_STATUS] = {
+		.op_func = nfsd4_offload_status,
+		.op_name = "OP_OFFLOAD_STATUS",
+		.op_rsize_bop = nfsd4_offload_status_rsize,
+	},
 };
 
 /**
fs/nfsd/nfs4xdr.c +8 -1
index 2c61c6b..ed8b61f 100644
@@ -1768,6 +1768,13 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
 }
 
 static __be32
+nfsd4_decode_offload_status(struct nfsd4_compoundargs *argp,
+			    struct nfsd4_offload_status *os)
+{
+	return nfsd4_decode_stateid(argp, &os->stateid);
+}
+
+static __be32
 nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek)
 {
 	DECODE_HEAD;
@@ -1874,7 +1881,7 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
 	[OP_LAYOUTERROR]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_LAYOUTSTATS]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_OFFLOAD_CANCEL]	= (nfsd4_dec)nfsd4_decode_notsupp,
-	[OP_OFFLOAD_STATUS]	= (nfsd4_dec)nfsd4_decode_notsupp,
+	[OP_OFFLOAD_STATUS]	= (nfsd4_dec)nfsd4_decode_offload_status,
 	[OP_READ_PLUS]		= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_SEEK]		= (nfsd4_dec)nfsd4_decode_seek,
 	[OP_WRITE_SAME]		= (nfsd4_dec)nfsd4_decode_notsupp,
@@ -4216,6 +4223,25 @@ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
 }

fs/nfsd/nfs4xdr.c static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp, · line 4233

Quoted 10 lines
fs/nfsd/nfs4xdr.c +8 -0
 
 static __be32
+nfsd4_encode_offload_status(struct nfsd4_compoundres *resp, __be32 nfserr,
+			    struct nfsd4_offload_status *os)
+{
+	struct xdr_stream *xdr = &resp->xdr;
+	__be32 *p;
+
+	if (nfserr)
+		return nfserr;

Note you can skip this--see upstream bac966d60652 and b7571e4cd39a.

--b.

Quoted 54 lines
fs/nfsd/nfs4xdr.c +12 -1
+
+	p = xdr_reserve_space(xdr, 8 + 4);
+	if (!p)
+		return nfserr_resource;
+	p = xdr_encode_hyper(p, os->count);
+	*p++ = cpu_to_be32(0);
+
+	return nfserr;
+}
+
+static __be32
 nfsd4_encode_seek(struct nfsd4_compoundres *resp, __be32 nfserr,
 		  struct nfsd4_seek *seek)
 {
@@ -4318,7 +4344,7 @@ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
 	[OP_LAYOUTERROR]	= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_LAYOUTSTATS]	= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_OFFLOAD_CANCEL]	= (nfsd4_enc)nfsd4_encode_noop,
-	[OP_OFFLOAD_STATUS]	= (nfsd4_enc)nfsd4_encode_noop,
+	[OP_OFFLOAD_STATUS]	= (nfsd4_enc)nfsd4_encode_offload_status,
 	[OP_READ_PLUS]		= (nfsd4_enc)nfsd4_encode_noop,
 	[OP_SEEK]		= (nfsd4_enc)nfsd4_encode_seek,
 	[OP_WRITE_SAME]		= (nfsd4_enc)nfsd4_encode_noop,
fs/nfsd/xdr4.h +10 -0
index 4ac2676..9b0c099 100644
@@ -542,6 +542,15 @@ struct nfsd4_seek {
 	loff_t		seek_pos;
 };
 
+struct nfsd4_offload_status {
+	/* request */
+	stateid_t	stateid;
+
+	/* response */
+	u64		count;
+	u32		status;
+};
+
 struct nfsd4_op {
 	int					opnum;
 	const struct nfsd4_operation *		opdesc;
@@ -600,6 +609,7 @@ struct nfsd4_op {
 		struct nfsd4_fallocate		deallocate;
 		struct nfsd4_clone		clone;
 		struct nfsd4_copy		copy;
+		struct nfsd4_offload_status	offload_status;
 		struct nfsd4_seek		seek;
 	} u;
 	struct nfs4_replay *			replay;
Signature · 1 line
1.8.3.1
#4 lore raw

PATCH v4 3/10 NFSD OFFLOAD_CANCEL xdr

In reply to Olga Kornievskaia
---
 fs/nfsd/nfs4proc.c | 13 +++++++++++++
 fs/nfsd/nfs4xdr.c  |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +13 -0
index 8601fc4..11ade90 100644
@@ -1121,6 +1121,14 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 }
 
 static __be32
+nfsd4_offload_cancel(struct svc_rqst *rqstp,
+		     struct nfsd4_compound_state *cstate,
+		     union nfsd4_op_u *u)
+{
+	return 0;
+}
+
+static __be32
 nfsd4_fallocate(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		struct nfsd4_fallocate *fallocate, int flags)
 {
@@ -2472,6 +2480,11 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 		.op_name = "OP_OFFLOAD_STATUS",
 		.op_rsize_bop = nfsd4_offload_status_rsize,
 	},
+	[OP_OFFLOAD_CANCEL] = {
+		.op_func = nfsd4_offload_cancel,
+		.op_name = "OP_OFFLOAD_CANCEL",
+		.op_rsize_bop = nfsd4_only_status_rsize,
+	},
 };
 
 /**
fs/nfsd/nfs4xdr.c +1 -1
index ed8b61f..31ee5e1 100644
@@ -1880,7 +1880,7 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
 	[OP_IO_ADVISE]		= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_LAYOUTERROR]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_LAYOUTSTATS]	= (nfsd4_dec)nfsd4_decode_notsupp,
-	[OP_OFFLOAD_CANCEL]	= (nfsd4_dec)nfsd4_decode_notsupp,
+	[OP_OFFLOAD_CANCEL]	= (nfsd4_dec)nfsd4_decode_offload_status,
 	[OP_OFFLOAD_STATUS]	= (nfsd4_dec)nfsd4_decode_offload_status,
 	[OP_READ_PLUS]		= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_SEEK]		= (nfsd4_dec)nfsd4_decode_seek,
Signature · 1 line
1.8.3.1
#5 lore raw

Re: [PATCH v4 03/10] NFSD OFFLOAD_CANCEL xdr

Quoted 26 lines · Olga Kornievskaia
---
 fs/nfsd/nfs4proc.c | 13 +++++++++++++
 fs/nfsd/nfs4xdr.c  |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +8 -0
index 8601fc4..11ade90 100644
@@ -1121,6 +1121,14 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 }
 
 static __be32
+nfsd4_offload_cancel(struct svc_rqst *rqstp,
+		     struct nfsd4_compound_state *cstate,
+		     union nfsd4_op_u *u)
+{
+	return 0;
+}
+
+static __be32
 nfsd4_fallocate(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		struct nfsd4_fallocate *fallocate, int flags)
 {

fs/nfsd/nfs4proc.c static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op) · line 2483

Quoted 4 lines
fs/nfsd/nfs4proc.c +1 -0
@@ -2472,6 +2480,11 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
 		.op_name = "OP_OFFLOAD_STATUS",
 		.op_rsize_bop = nfsd4_offload_status_rsize,
 	},
+	[OP_OFFLOAD_CANCEL] = {

I think you want .op_flags = OP_MODIFIES_SOMETHING as well.

Quoted 23 lines
fs/nfsd/nfs4proc.c +4 -0
+		.op_func = nfsd4_offload_cancel,
+		.op_name = "OP_OFFLOAD_CANCEL",
+		.op_rsize_bop = nfsd4_only_status_rsize,
+	},
 };
 
 /**
fs/nfsd/nfs4xdr.c +1 -1
index ed8b61f..31ee5e1 100644
@@ -1880,7 +1880,7 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
 	[OP_IO_ADVISE]		= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_LAYOUTERROR]	= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_LAYOUTSTATS]	= (nfsd4_dec)nfsd4_decode_notsupp,
-	[OP_OFFLOAD_CANCEL]	= (nfsd4_dec)nfsd4_decode_notsupp,
+	[OP_OFFLOAD_CANCEL]	= (nfsd4_dec)nfsd4_decode_offload_status,
 	[OP_OFFLOAD_STATUS]	= (nfsd4_dec)nfsd4_decode_offload_status,
 	[OP_READ_PLUS]		= (nfsd4_dec)nfsd4_decode_notsupp,
 	[OP_SEEK]		= (nfsd4_dec)nfsd4_decode_seek,
Signature · 1 line
1.8.3.1
#6 lore raw

Re: [PATCH v4 03/10] NFSD OFFLOAD_CANCEL xdr

Quoted 25 lines · J. Bruce Fields
Quoted 24 lines · Olga Kornievskaia
---
 fs/nfsd/nfs4proc.c | 13 +++++++++++++
 fs/nfsd/nfs4xdr.c  |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +8 -0
index 8601fc4..11ade90 100644
@@ -1121,6 +1121,14 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 }
 
 static __be32
+nfsd4_offload_cancel(struct svc_rqst *rqstp,
+                  struct nfsd4_compound_state *cstate,
+                  union nfsd4_op_u *u)
+{
+     return 0;
+}
+
+static __be32
 nfsd4_fallocate(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
              struct nfsd4_fallocate *fallocate, int flags)
Quoted 8 lines

fs/nfsd/nfs4proc.c static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op) · line 2483

Quoted 6 lines
fs/nfsd/nfs4proc.c +1 -0
 {
@@ -2472,6 +2480,11 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
              .op_name = "OP_OFFLOAD_STATUS",
              .op_rsize_bop = nfsd4_offload_status_rsize,
      },
+     [OP_OFFLOAD_CANCEL] = {

I think you want .op_flags = OP_MODIFIES_SOMETHING as well.

Got it. Is it because it modifies state of the server? I guess I'm not clear what OP_MODIFIES_SOMETHING represents.

Quoted 27 lines
Quoted 21 lines
fs/nfsd/nfs4proc.c +4 -0
+             .op_func = nfsd4_offload_cancel,
+             .op_name = "OP_OFFLOAD_CANCEL",
+             .op_rsize_bop = nfsd4_only_status_rsize,
+     },
 };
 
 /**
fs/nfsd/nfs4xdr.c +1 -2
index ed8b61f..31ee5e1 100644
@@ -1880,7 +1880,7 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
      [OP_IO_ADVISE]          = (nfsd4_dec)nfsd4_decode_notsupp,
      [OP_LAYOUTERROR]        = (nfsd4_dec)nfsd4_decode_notsupp,
      [OP_LAYOUTSTATS]        = (nfsd4_dec)nfsd4_decode_notsupp,
-     [OP_OFFLOAD_CANCEL]     = (nfsd4_dec)nfsd4_decode_notsupp,
+     [OP_OFFLOAD_CANCEL]     = (nfsd4_dec)nfsd4_decode_offload_status,
      [OP_OFFLOAD_STATUS]     = (nfsd4_dec)nfsd4_decode_offload_status,
      [OP_READ_PLUS]          = (nfsd4_dec)nfsd4_decode_notsupp,
      [OP_SEEK]               = (nfsd4_dec)nfsd4_decode_seek,
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#7 lore raw

Re: [PATCH v4 03/10] NFSD OFFLOAD_CANCEL xdr

Quoted 35 lines · Olga Kornievskaia
Quoted 25 lines · J. Bruce Fields
Quoted 24 lines · Olga Kornievskaia
---
 fs/nfsd/nfs4proc.c | 13 +++++++++++++
 fs/nfsd/nfs4xdr.c  |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +8 -0
index 8601fc4..11ade90 100644
@@ -1121,6 +1121,14 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 }
 
 static __be32
+nfsd4_offload_cancel(struct svc_rqst *rqstp,
+                  struct nfsd4_compound_state *cstate,
+                  union nfsd4_op_u *u)
+{
+     return 0;
+}
+
+static __be32
 nfsd4_fallocate(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
              struct nfsd4_fallocate *fallocate, int flags)
Quoted 8 lines

fs/nfsd/nfs4proc.c static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op) · line 2483

Quoted 6 lines
fs/nfsd/nfs4proc.c +1 -0
 {
@@ -2472,6 +2480,11 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
              .op_name = "OP_OFFLOAD_STATUS",
              .op_rsize_bop = nfsd4_offload_status_rsize,
      },
+     [OP_OFFLOAD_CANCEL] = {

I think you want .op_flags = OP_MODIFIES_SOMETHING as well.

Quoted 2 lines

Got it. Is it because it modifies state of the server? I guess I'm not clear what OP_MODIFIES_SOMETHING represents.

If OP_MODIFIES_SOMETHING is set then we will refuse to perform the operation if it looks like we might run out of space to encode the result.

Eh, now that I think about it I'm not actually sure if it matters for an operation like this. Probably doesn't hurt.

--b.

Quoted 29 lines
Quoted 29 lines
Quoted 23 lines
fs/nfsd/nfs4proc.c +4 -0
+             .op_func = nfsd4_offload_cancel,
+             .op_name = "OP_OFFLOAD_CANCEL",
+             .op_rsize_bop = nfsd4_only_status_rsize,
+     },
 };
 
 /**
fs/nfsd/nfs4xdr.c +1 -2
index ed8b61f..31ee5e1 100644
@@ -1880,7 +1880,7 @@ static __be32 nfsd4_decode_reclaim_complete(struct nfsd4_compoundargs *argp, str
      [OP_IO_ADVISE]          = (nfsd4_dec)nfsd4_decode_notsupp,
      [OP_LAYOUTERROR]        = (nfsd4_dec)nfsd4_decode_notsupp,
      [OP_LAYOUTSTATS]        = (nfsd4_dec)nfsd4_decode_notsupp,
-     [OP_OFFLOAD_CANCEL]     = (nfsd4_dec)nfsd4_decode_notsupp,
+     [OP_OFFLOAD_CANCEL]     = (nfsd4_dec)nfsd4_decode_offload_status,
      [OP_OFFLOAD_STATUS]     = (nfsd4_dec)nfsd4_decode_offload_status,
      [OP_READ_PLUS]          = (nfsd4_dec)nfsd4_decode_notsupp,
      [OP_SEEK]               = (nfsd4_dec)nfsd4_decode_seek,
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#8 lore raw

PATCH v4 4/10 NFSD xdr callback stateid in async COPY reply

In reply to Olga Kornievskaia
---
 fs/nfsd/nfs4xdr.c | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)
fs/nfsd/nfs4xdr.c +17 -4
index 31ee5e1..5f5058e 100644
@@ -4190,15 +4190,27 @@ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
 #endif /* CONFIG_NFSD_PNFS */
 
 static __be32
-nfsd42_encode_write_res(struct nfsd4_compoundres *resp, struct nfsd42_write_res *write)
+nfsd42_encode_write_res(struct nfsd4_compoundres *resp,
+		struct nfsd42_write_res *write, bool sync)
 {
 	__be32 *p;
+	p = xdr_reserve_space(&resp->xdr, 4);
+	if (!p)
+		return nfserr_resource;
 
-	p = xdr_reserve_space(&resp->xdr, 4 + 8 + 4 + NFS4_VERIFIER_SIZE);
+	if (sync)
+		*p++ = cpu_to_be32(0);
+	else {
+		__be32 nfserr;
+		*p++ = cpu_to_be32(1);
+		nfserr = nfsd4_encode_stateid(&resp->xdr, &write->cb_stateid);
+		if (nfserr)
+			return nfserr;
+	}
+	p = xdr_reserve_space(&resp->xdr, 8 + 4 + NFS4_VERIFIER_SIZE);
 	if (!p)
 		return nfserr_resource;
 
-	*p++ = cpu_to_be32(0);
 	p = xdr_encode_hyper(p, write->wr_bytes_written);
 	*p++ = cpu_to_be32(write->wr_stable_how);
 	p = xdr_encode_opaque_fixed(p, write->wr_verifier.data,
@@ -4212,7 +4224,8 @@ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
 {
 	__be32 *p;
 
-	nfserr = nfsd42_encode_write_res(resp, &copy->cp_res);
+	nfserr = nfsd42_encode_write_res(resp, &copy->cp_res,
+			copy->cp_synchronous);
 	if (nfserr)
 		return nfserr;
 
Signature · 1 line
1.8.3.1
#9 lore raw

PATCH v4 5/10 NFSD first draft of async copy

In reply to Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy. In case a failure happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results.

---
 fs/nfsd/nfs4proc.c  | 164 ++++++++++++++++++++++++++++++++++++++++++++++------
 fs/nfsd/nfs4state.c |   9 ++-
 fs/nfsd/state.h     |   2 +
 fs/nfsd/xdr4.h      |   7 +++
 4 files changed, 162 insertions(+), 20 deletions(-)
fs/nfsd/nfs4proc.c +145 -19
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif
 
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+	copy_wq = create_singlethread_workqueue("nfsd4_copy");
+	if (!copy_wq)
+		return -ENOMEM;
+	return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
+{
+	destroy_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 out:
 	return status;
 }
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+	struct nfsd4_copy *copy = container_of(cb, struct nfsd4_copy, cp_cb);
+
+	kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+				 struct rpc_task *task)
+{
+	return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops = {
+	.release = nfsd4_cb_offload_release,
+	.done = nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+		sizeof(copy->cp_dst_stateid));
+	copy->cp_res.wr_stable_how = NFS_UNSTABLE;
+	copy->cp_consecutive = 1;
+	copy->cp_synchronous = sync;
+	gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+	return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+	ssize_t bytes_copied = 0;
+	size_t bytes_total = copy->cp_count;
+	size_t bytes_to_copy;
+	u64 src_pos = copy->cp_src_pos;
+	u64 dst_pos = copy->cp_dst_pos;
+
+	do {
+		bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
+		bytes_copied = nfsd_copy_file_range(copy->fh_src, src_pos,
+					copy->fh_dst, dst_pos, bytes_to_copy);
+		if (bytes_copied <= 0)
+			break;
+		bytes_total -= bytes_copied;
+		copy->cp_res.wr_bytes_written += bytes_copied;
+		src_pos += bytes_copied;
+		dst_pos += bytes_copied;
+	} while (bytes_total > 0 && !copy->cp_synchronous);
+	return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+	__be32 status;
+	ssize_t bytes;
+
+	bytes = _nfsd_copy_file_range(copy);
+	if (bytes < 0)
+		status = nfserrno(bytes);
+	else
+		status = nfsd4_init_copy_res(copy, sync);
+
+	fput(copy->fh_src);
+	fput(copy->fh_dst);
+	return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
+{
+	memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, sizeof(stateid_t));
+	memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, sizeof(stateid_t));
+	dst->cp_src_pos = src->cp_src_pos;
+	dst->cp_dst_pos = src->cp_dst_pos;
+	dst->cp_count = src->cp_count;
+	dst->cp_consecutive = src->cp_consecutive;
+	dst->cp_synchronous = src->cp_synchronous;
+	memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+	/* skipping nfsd4_callback */
+	memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+	dst->fh = src->fh;
+	dst->cp_clp = src->cp_clp;
+	dst->fh_src = src->fh_src;
+	dst->fh_dst = src->fh_dst;
+	dst->net = src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+	struct nfsd4_copy *copy =
+		container_of(work, struct nfsd4_copy, cp_work);
+	struct nfsd4_copy *cb_copy;
+
+	copy->nfserr = nfsd4_do_copy(copy, 0);
+	cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+	if (!cb_copy)
+		goto out;
+	memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+	cb_copy->cp_clp = copy->cp_clp;
+	cb_copy->nfserr = copy->nfserr;
+	memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+	nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+			&nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
+	nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+	kfree(copy);
+}
 
 static __be32
 nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		union nfsd4_op_u *u)
 {
 	struct nfsd4_copy *copy = &u->copy;
-	struct file *src, *dst;
 	__be32 status;
-	ssize_t bytes;
 
-	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid, &src,
-				   &copy->cp_dst_stateid, &dst);
+	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
+				   &copy->fh_src, &copy->cp_dst_stateid,
+				   &copy->fh_dst);
 	if (status)
 		goto out;
 
-	bytes = nfsd_copy_file_range(src, copy->cp_src_pos,
-			dst, copy->cp_dst_pos, copy->cp_count);
-
-	if (bytes < 0)
-		status = nfserrno(bytes);
-	else {
-		copy->cp_res.wr_bytes_written = bytes;
-		copy->cp_res.wr_stable_how = NFS_UNSTABLE;
-		copy->cp_consecutive = 1;
-		copy->cp_synchronous = 1;
-		gen_boot_verifier(&copy->cp_res.wr_verifier, SVC_NET(rqstp));
-		status = nfs_ok;
+	copy->cp_clp = cstate->clp;
+	memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+		sizeof(struct knfsd_fh));
+	copy->net = SVC_NET(rqstp);
+	if (!copy->cp_synchronous) {
+		struct nfsd4_copy *async_copy;
+
+		status = nfsd4_init_copy_res(copy, 0);
+		async_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+		if (!async_copy) {
+			status = nfserrno(-ENOMEM);
+			goto out;
+		}
+		dup_copy_fields(copy, async_copy);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+			sizeof(copy->cp_dst_stateid));
+		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+		queue_work(copy_wq, &async_copy->cp_work);
+	} else {
+		status = nfsd4_do_copy(copy, 1);
 	}
-
-	fput(src);
-	fput(dst);
 out:
 	return status;
 }
fs/nfsd/nfs4state.c +8 -1
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net *net)
 		goto out_free_laundry;
 
 	set_max_delegations();
-	return 0;
 
+	ret = nfsd4_create_copy_queue();
+	if (ret)
+		goto out_free_callback;
+
+	return 0;
+out_free_callback:
+	nfsd4_destroy_callback_queue();
 out_free_laundry:
 	destroy_workqueue(laundry_wq);
 out_cleanup_cred:
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net *net)
 	destroy_workqueue(laundry_wq);
 	nfsd4_destroy_callback_queue();
 	cleanup_callback_cred();
+	nfsd4_destroy_copy_queue();
 }
 
 static void
fs/nfsd/state.h +2 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 							struct nfsd_net *nn);
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
fs/nfsd/xdr4.h +7 -0
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
 	struct nfsd4_callback	cp_cb;
 	__be32			nfserr;
 	struct knfsd_fh		fh;
+
+	struct work_struct      cp_work;
+	struct nfs4_client      *cp_clp;
+
+	struct file             *fh_src;
+	struct file             *fh_dst;
+	struct net              *net;
 };
 
 struct nfsd4_seek {
Signature · 1 line
1.8.3.1
#10 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

Quoted 8 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.

The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case.

--b.

Quoted 269 lines

In case a failure
happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results.

---
 fs/nfsd/nfs4proc.c  | 164 ++++++++++++++++++++++++++++++++++++++++++++++------
 fs/nfsd/nfs4state.c |   9 ++-
 fs/nfsd/state.h     |   2 +
 fs/nfsd/xdr4.h      |   7 +++
 4 files changed, 162 insertions(+), 20 deletions(-)
fs/nfsd/nfs4proc.c +145 -19
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif
 
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+	copy_wq = create_singlethread_workqueue("nfsd4_copy");
+	if (!copy_wq)
+		return -ENOMEM;
+	return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
+{
+	destroy_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 out:
 	return status;
 }
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+	struct nfsd4_copy *copy = container_of(cb, struct nfsd4_copy, cp_cb);
+
+	kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+				 struct rpc_task *task)
+{
+	return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops = {
+	.release = nfsd4_cb_offload_release,
+	.done = nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+		sizeof(copy->cp_dst_stateid));
+	copy->cp_res.wr_stable_how = NFS_UNSTABLE;
+	copy->cp_consecutive = 1;
+	copy->cp_synchronous = sync;
+	gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+	return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+	ssize_t bytes_copied = 0;
+	size_t bytes_total = copy->cp_count;
+	size_t bytes_to_copy;
+	u64 src_pos = copy->cp_src_pos;
+	u64 dst_pos = copy->cp_dst_pos;
+
+	do {
+		bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
+		bytes_copied = nfsd_copy_file_range(copy->fh_src, src_pos,
+					copy->fh_dst, dst_pos, bytes_to_copy);
+		if (bytes_copied <= 0)
+			break;
+		bytes_total -= bytes_copied;
+		copy->cp_res.wr_bytes_written += bytes_copied;
+		src_pos += bytes_copied;
+		dst_pos += bytes_copied;
+	} while (bytes_total > 0 && !copy->cp_synchronous);
+	return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+	__be32 status;
+	ssize_t bytes;
+
+	bytes = _nfsd_copy_file_range(copy);
+	if (bytes < 0)
+		status = nfserrno(bytes);
+	else
+		status = nfsd4_init_copy_res(copy, sync);
+
+	fput(copy->fh_src);
+	fput(copy->fh_dst);
+	return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
+{
+	memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, sizeof(stateid_t));
+	memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, sizeof(stateid_t));
+	dst->cp_src_pos = src->cp_src_pos;
+	dst->cp_dst_pos = src->cp_dst_pos;
+	dst->cp_count = src->cp_count;
+	dst->cp_consecutive = src->cp_consecutive;
+	dst->cp_synchronous = src->cp_synchronous;
+	memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+	/* skipping nfsd4_callback */
+	memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+	dst->fh = src->fh;
+	dst->cp_clp = src->cp_clp;
+	dst->fh_src = src->fh_src;
+	dst->fh_dst = src->fh_dst;
+	dst->net = src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+	struct nfsd4_copy *copy =
+		container_of(work, struct nfsd4_copy, cp_work);
+	struct nfsd4_copy *cb_copy;
+
+	copy->nfserr = nfsd4_do_copy(copy, 0);
+	cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+	if (!cb_copy)
+		goto out;
+	memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+	cb_copy->cp_clp = copy->cp_clp;
+	cb_copy->nfserr = copy->nfserr;
+	memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+	nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+			&nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
+	nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+	kfree(copy);
+}
 
 static __be32
 nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		union nfsd4_op_u *u)
 {
 	struct nfsd4_copy *copy = &u->copy;
-	struct file *src, *dst;
 	__be32 status;
-	ssize_t bytes;
 
-	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid, &src,
-				   &copy->cp_dst_stateid, &dst);
+	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
+				   &copy->fh_src, &copy->cp_dst_stateid,
+				   &copy->fh_dst);
 	if (status)
 		goto out;
 
-	bytes = nfsd_copy_file_range(src, copy->cp_src_pos,
-			dst, copy->cp_dst_pos, copy->cp_count);
-
-	if (bytes < 0)
-		status = nfserrno(bytes);
-	else {
-		copy->cp_res.wr_bytes_written = bytes;
-		copy->cp_res.wr_stable_how = NFS_UNSTABLE;
-		copy->cp_consecutive = 1;
-		copy->cp_synchronous = 1;
-		gen_boot_verifier(&copy->cp_res.wr_verifier, SVC_NET(rqstp));
-		status = nfs_ok;
+	copy->cp_clp = cstate->clp;
+	memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+		sizeof(struct knfsd_fh));
+	copy->net = SVC_NET(rqstp);
+	if (!copy->cp_synchronous) {
+		struct nfsd4_copy *async_copy;
+
+		status = nfsd4_init_copy_res(copy, 0);
+		async_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+		if (!async_copy) {
+			status = nfserrno(-ENOMEM);
+			goto out;
+		}
+		dup_copy_fields(copy, async_copy);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+			sizeof(copy->cp_dst_stateid));
+		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+		queue_work(copy_wq, &async_copy->cp_work);
+	} else {
+		status = nfsd4_do_copy(copy, 1);
 	}
-
-	fput(src);
-	fput(dst);
 out:
 	return status;
 }
fs/nfsd/nfs4state.c +8 -1
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net *net)
 		goto out_free_laundry;
 
 	set_max_delegations();
-	return 0;
 
+	ret = nfsd4_create_copy_queue();
+	if (ret)
+		goto out_free_callback;
+
+	return 0;
+out_free_callback:
+	nfsd4_destroy_callback_queue();
 out_free_laundry:
 	destroy_workqueue(laundry_wq);
 out_cleanup_cred:
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net *net)
 	destroy_workqueue(laundry_wq);
 	nfsd4_destroy_callback_queue();
 	cleanup_callback_cred();
+	nfsd4_destroy_copy_queue();
 }
 
 static void
fs/nfsd/state.h +2 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 							struct nfsd_net *nn);
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
fs/nfsd/xdr4.h +7 -0
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
 	struct nfsd4_callback	cp_cb;
 	__be32			nfserr;
 	struct knfsd_fh		fh;
+
+	struct work_struct      cp_work;
+	struct nfs4_client      *cp_clp;
+
+	struct file             *fh_src;
+	struct file             *fh_dst;
+	struct net              *net;
 };
 
 struct nfsd4_seek {
Signature · 1 line
1.8.3.1
#11 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

Quoted 2 lines

On Sep 28, 2017, at 2:07 PM, J. Bruce Fields <[email protected]> =

Quoted 10 lines · wrote:

=20

Quoted 8 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.
=20
nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

Quoted 7 lines

=20
I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.
=20
The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case. =20

One reason is to allow for the copy to be cancelled in the =E2=80=9Cmiddle= =E2=80=9D.
Otherwise, we have no control over once we call into the = vfs_copy_file_range
to stop the on-going copy. At least this way we check every 4MB chunks.

Quoted 10 lines

--b.
=20

Quoted 8 lines

In case a failure
happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results. =20

---
fs/nfsd/nfs4proc.c | 164 =

++++++++++++++++++++++++++++++++++++++++++++++------

Quoted 24 lines
Quoted 24 lines
fs/nfsd/nfs4state.c |   9 ++-
fs/nfsd/state.h     |   2 +
fs/nfsd/xdr4.h      |   7 +++
4 files changed, 162 insertions(+), 20 deletions(-)

=20

fs/nfsd/nfs4proc.c +0 -0
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif

=20

fs/nfsd/nfs4proc.c +11 -0
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+	copy_wq =3D create_singlethread_workqueue("nfsd4_copy");
+	if (!copy_wq)
+		return -ENOMEM;
+	return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
Quoted 8 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec =

Quoted 8 lines
fs/nfsd/nfs4proc.c +4 -0
+{
+	destroy_workqueue(copy_wq);
+}
+

#define NFSDDBG_FACILITY NFSDDBG_PROC
=20
static u32 nfsd_attrmask[] =3D {

fs/nfsd/nfs4proc.c +0 -0
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec =

*vec, struct nfsd4_write *write)

Quoted 6 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

Quoted 6 lines

out:

	return status;
}
fs/nfsd/nfs4proc.c +3 -0
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+	struct nfsd4_copy *copy =3D container_of(cb, struct nfsd4_copy, =

cp_cb);

Quoted 30 lines
Quoted 30 lines
fs/nfsd/nfs4proc.c +30 -0
+
+	kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+				 struct rpc_task *task)
+{
+	return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops =3D {
+	.release =3D nfsd4_cb_offload_release,
+	.done =3D nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+		sizeof(copy->cp_dst_stateid));
+	copy->cp_res.wr_stable_how =3D NFS_UNSTABLE;
+	copy->cp_consecutive =3D 1;
+	copy->cp_synchronous =3D sync;
+	gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+	return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+	ssize_t bytes_copied =3D 0;
Quoted 8 lines

fs/nfsd/nfs4proc.c

Quoted 8 lines
fs/nfsd/nfs4proc.c +8 -0
+	size_t bytes_total =3D copy->cp_count;
+	size_t bytes_to_copy;
+	u64 src_pos =3D copy->cp_src_pos;
+	u64 dst_pos =3D copy->cp_dst_pos;
+
+	do {
+		bytes_to_copy =3D min_t(u64, bytes_total, MAX_RW_COUNT);
+		bytes_copied =3D nfsd_copy_file_range(copy->fh_src, =

src_pos,

Quoted 1 line

fs/nfsd/nfs4proc.c

Quoted 1 line
fs/nfsd/nfs4proc.c +1 -0
+					copy->fh_dst, dst_pos, =

bytes_to_copy);

Quoted 19 lines
Quoted 19 lines
fs/nfsd/nfs4proc.c +19 -0
+		if (bytes_copied <=3D 0)
+			break;
+		bytes_total -=3D bytes_copied;
+		copy->cp_res.wr_bytes_written +=3D bytes_copied;
+		src_pos +=3D bytes_copied;
+		dst_pos +=3D bytes_copied;
+	} while (bytes_total > 0 && !copy->cp_synchronous);
+	return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+	__be32 status;
+	ssize_t bytes;
+
+	bytes =3D _nfsd_copy_file_range(copy);
+	if (bytes < 0)
+		status =3D nfserrno(bytes);
+	else
Quoted 8 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

Quoted 8 lines
fs/nfsd/nfs4proc.c +8 -0
+		status =3D nfsd4_init_copy_res(copy, sync);
+
+	fput(copy->fh_src);
+	fput(copy->fh_dst);
+	return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct =

nfsd4_copy *dst)

Quoted 2 lines

fs/nfsd/nfs4proc.c

Quoted 2 lines
fs/nfsd/nfs4proc.c +2 -0
+{
+	memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, =

sizeof(stateid_t));

Quoted 1 line

fs/nfsd/nfs4proc.c

Quoted 1 line
fs/nfsd/nfs4proc.c +1 -0
+	memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, =

sizeof(stateid_t));

Quoted 23 lines
Quoted 23 lines
fs/nfsd/nfs4proc.c +23 -0
+	dst->cp_src_pos =3D src->cp_src_pos;
+	dst->cp_dst_pos =3D src->cp_dst_pos;
+	dst->cp_count =3D src->cp_count;
+	dst->cp_consecutive =3D src->cp_consecutive;
+	dst->cp_synchronous =3D src->cp_synchronous;
+	memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+	/* skipping nfsd4_callback */
+	memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+	dst->fh =3D src->fh;
+	dst->cp_clp =3D src->cp_clp;
+	dst->fh_src =3D src->fh_src;
+	dst->fh_dst =3D src->fh_dst;
+	dst->net =3D src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+	struct nfsd4_copy *copy =3D
+		container_of(work, struct nfsd4_copy, cp_work);
+	struct nfsd4_copy *cb_copy;
+
+	copy->nfserr =3D nfsd4_do_copy(copy, 0);
+	cb_copy =3D kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

Quoted 8 lines
fs/nfsd/nfs4proc.c +8 -0
+	if (!cb_copy)
+		goto out;
+	memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+	cb_copy->cp_clp =3D copy->cp_clp;
+	cb_copy->nfserr =3D copy->nfserr;
+	memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+	nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+			&nfsd4_cb_offload_ops, =

NFSPROC4_CLNT_CB_OFFLOAD);

Quoted 7 lines
Quoted 7 lines
fs/nfsd/nfs4proc.c +4 -0
+	nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+	kfree(copy);
+}

=20
static __be32
nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state =

*cstate,

Quoted 8 lines

fs/nfsd/nfs4proc.c

Quoted 8 lines

union nfsd4_op_u *u)

{
	struct nfsd4_copy *copy =3D &u->copy;
fs/nfsd/nfs4proc.c +0 -1
-	struct file *src, *dst;
	__be32 status;
fs/nfsd/nfs4proc.c +0 -1
-	ssize_t bytes;

=20

fs/nfsd/nfs4proc.c +0 -1
-	status =3D nfsd4_verify_copy(rqstp, cstate, =

&copy->cp_src_stateid, &src,

Quoted 2 lines

fs/nfsd/nfs4proc.c

Quoted 2 lines
fs/nfsd/nfs4proc.c +1 -1
-				   &copy->cp_dst_stateid, &dst);
+	status =3D nfsd4_verify_copy(rqstp, cstate, =

&copy->cp_src_stateid,

Quoted 8 lines
Quoted 8 lines
fs/nfsd/nfs4proc.c +2 -0
+				   &copy->fh_src, &copy->cp_dst_stateid,
+				   &copy->fh_dst);
	if (status)
		goto out;

=20

fs/nfsd/nfs4proc.c +0 -3
-	bytes =3D nfsd_copy_file_range(src, copy->cp_src_pos,
-			dst, copy->cp_dst_pos, copy->cp_count);
-
Quoted 8 lines

fs/nfsd/nfs4proc.c

Quoted 8 lines
fs/nfsd/nfs4proc.c +0 -8
-	if (bytes < 0)
-		status =3D nfserrno(bytes);
-	else {
-		copy->cp_res.wr_bytes_written =3D bytes;
-		copy->cp_res.wr_stable_how =3D NFS_UNSTABLE;
-		copy->cp_consecutive =3D 1;
-		copy->cp_synchronous =3D 1;
-		gen_boot_verifier(&copy->cp_res.wr_verifier, =

SVC_NET(rqstp));

Quoted 10 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

Quoted 10 lines
fs/nfsd/nfs4proc.c +9 -1
-		status =3D nfs_ok;
+	copy->cp_clp =3D cstate->clp;
+	memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+		sizeof(struct knfsd_fh));
+	copy->net =3D SVC_NET(rqstp);
+	if (!copy->cp_synchronous) {
+		struct nfsd4_copy *async_copy;
+
+		status =3D nfsd4_init_copy_res(copy, 0);
+		async_copy =3D kzalloc(sizeof(struct nfsd4_copy), =

GFP_KERNEL);

Quoted 15 lines
Quoted 15 lines
fs/nfsd/nfs4proc.c +11 -0
+		if (!async_copy) {
+			status =3D nfserrno(-ENOMEM);
+			goto out;
+		}
+		dup_copy_fields(copy, async_copy);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+			sizeof(copy->cp_dst_stateid));
+		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+		queue_work(copy_wq, &async_copy->cp_work);
+	} else {
+		status =3D nfsd4_do_copy(copy, 1);
	}
fs/nfsd/nfs4proc.c +0 -3
-
-	fput(src);
-	fput(dst);
Quoted 8 lines

fs/nfsd/nfs4state.c static int nfs4_state_create_net(struct net =

Quoted 8 lines

out:

	return status;
}
fs/nfsd/nfs4state.c +0 -0
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net =

*net)

Quoted 8 lines
Quoted 8 lines
fs/nfsd/nfs4state.c +0 -0
 		goto out_free_laundry;

=20

	set_max_delegations();
fs/nfsd/nfs4state.c +0 -1
-	return 0;

=20

fs/nfsd/nfs4state.c +3 -0
+	ret =3D nfsd4_create_copy_queue();
+	if (ret)
+		goto out_free_callback;
Quoted 8 lines

fs/nfsd/nfs4state.c static int nfs4_state_create_net(struct net =

Quoted 8 lines
fs/nfsd/nfs4state.c +4 -0
+
+	return 0;
+out_free_callback:
+	nfsd4_destroy_callback_queue();

out_free_laundry:

	destroy_workqueue(laundry_wq);

out_cleanup_cred:

fs/nfsd/nfs4state.c +0 -0
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net =

*net)

Quoted 4 lines
Quoted 4 lines
fs/nfsd/nfs4state.c +1 -0
 	destroy_workqueue(laundry_wq);
 	nfsd4_destroy_callback_queue();
 	cleanup_callback_cred();
+	nfsd4_destroy_copy_queue();
Quoted 8 lines

fs/nfsd/state.h extern void nfsd4_init_cb(struct nfsd4_callback =

Quoted 8 lines
fs/nfsd/nfs4state.c +0 -0
 }

=20
static void

fs/nfsd/state.h +0 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback =

*cb, struct nfs4_client *clp,

Quoted 1 line
Quoted 1 line

extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char =

*name,

Quoted 1 line
Quoted 1 line

struct nfsd_net =

*nn);

Quoted 1 line
Quoted 1 line

extern bool nfs4_has_reclaimed_state(const char *name, struct =

nfsd_net *nn);

Quoted 26 lines
Quoted 26 lines
fs/nfsd/state.h +2 -0
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);

=20
struct nfs4_file *find_file(struct knfsd_fh *fh);
void put_nfs4_file(struct nfs4_file *fi);

fs/nfsd/xdr4.h +7 -0
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
 	struct nfsd4_callback	cp_cb;
 	__be32			nfserr;
 	struct knfsd_fh		fh;
+
+	struct work_struct      cp_work;
+	struct nfs4_client      *cp_clp;
+
+	struct file             *fh_src;
+	struct file             *fh_dst;
+	struct net              *net;
 };

=20
struct nfsd4_seek {
--=20
1.8.3.1
=20

#12 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

Quoted 16 lines · Olga Kornievskaia
Quoted 16 lines · J. Bruce Fields
Quoted 8 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.

Quoted 7 lines
Quoted 3 lines

The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case.

One reason is to allow for the copy to be cancelled in the “middle”. Otherwise, we have no control over once we call into the vfs_copy_file_range to stop the on-going copy. At least this way we check every 4MB chunks.

Yes, see my other email, I think we should signal the thread to stop it.

Even with copying in chunks--we'd like to be able to cancel the copy mid-chunk.

Talking this over here with Jeff and Trond.... I don't *think* there's an easy way to cancel a long-running work item.

So probably you want to create your own thread for the copy instead. It looks like kthread_create/kthread_stop/kthread_should_stop are what you want? You can see some examples if you "git grep kthread net/sunrpc".

--b.

Quoted 273 lines
Quoted 272 lines

--b.

Quoted 269 lines

In case a failure
happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results.

---

fs/nfsd/nfs4proc.c  | 164 ++++++++++++++++++++++++++++++++++++++++++++++------
fs/nfsd/nfs4state.c |   9 ++-
fs/nfsd/state.h     |   2 +
fs/nfsd/xdr4.h      |   7 +++
4 files changed, 162 insertions(+), 20 deletions(-)
fs/nfsd/nfs4proc.c +15 -0
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif
 
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+	copy_wq = create_singlethread_workqueue("nfsd4_copy");
+	if (!copy_wq)
+		return -ENOMEM;
+	return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
+{
+	destroy_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

out:

	return status;
}
fs/nfsd/nfs4proc.c +107 -0
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+	struct nfsd4_copy *copy = container_of(cb, struct nfsd4_copy, cp_cb);
+
+	kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+				 struct rpc_task *task)
+{
+	return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops = {
+	.release = nfsd4_cb_offload_release,
+	.done = nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+		sizeof(copy->cp_dst_stateid));
+	copy->cp_res.wr_stable_how = NFS_UNSTABLE;
+	copy->cp_consecutive = 1;
+	copy->cp_synchronous = sync;
+	gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+	return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+	ssize_t bytes_copied = 0;
+	size_t bytes_total = copy->cp_count;
+	size_t bytes_to_copy;
+	u64 src_pos = copy->cp_src_pos;
+	u64 dst_pos = copy->cp_dst_pos;
+
+	do {
+		bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
+		bytes_copied = nfsd_copy_file_range(copy->fh_src, src_pos,
+					copy->fh_dst, dst_pos, bytes_to_copy);
+		if (bytes_copied <= 0)
+			break;
+		bytes_total -= bytes_copied;
+		copy->cp_res.wr_bytes_written += bytes_copied;
+		src_pos += bytes_copied;
+		dst_pos += bytes_copied;
+	} while (bytes_total > 0 && !copy->cp_synchronous);
+	return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+	__be32 status;
+	ssize_t bytes;
+
+	bytes = _nfsd_copy_file_range(copy);
+	if (bytes < 0)
+		status = nfserrno(bytes);
+	else
+		status = nfsd4_init_copy_res(copy, sync);
+
+	fput(copy->fh_src);
+	fput(copy->fh_dst);
+	return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
+{
+	memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, sizeof(stateid_t));
+	memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, sizeof(stateid_t));
+	dst->cp_src_pos = src->cp_src_pos;
+	dst->cp_dst_pos = src->cp_dst_pos;
+	dst->cp_count = src->cp_count;
+	dst->cp_consecutive = src->cp_consecutive;
+	dst->cp_synchronous = src->cp_synchronous;
+	memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+	/* skipping nfsd4_callback */
+	memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+	dst->fh = src->fh;
+	dst->cp_clp = src->cp_clp;
+	dst->fh_src = src->fh_src;
+	dst->fh_dst = src->fh_dst;
+	dst->net = src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+	struct nfsd4_copy *copy =
+		container_of(work, struct nfsd4_copy, cp_work);
+	struct nfsd4_copy *cb_copy;
+
+	copy->nfserr = nfsd4_do_copy(copy, 0);
+	cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+	if (!cb_copy)
+		goto out;
+	memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+	cb_copy->cp_clp = copy->cp_clp;
+	cb_copy->nfserr = copy->nfserr;
+	memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+	nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+			&nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
+	nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+	kfree(copy);
+}

static __be32
nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
union nfsd4_op_u *u)

{
	struct nfsd4_copy *copy = &u->copy;
fs/nfsd/nfs4proc.c +0 -1
-	struct file *src, *dst;
	__be32 status;
fs/nfsd/nfs4proc.c +3 -3
-	ssize_t bytes;
 
-	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid, &src,
-				   &copy->cp_dst_stateid, &dst);
+	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
+				   &copy->fh_src, &copy->cp_dst_stateid,
+				   &copy->fh_dst);
	if (status)
		goto out;
fs/nfsd/nfs4proc.c +20 -12
-	bytes = nfsd_copy_file_range(src, copy->cp_src_pos,
-			dst, copy->cp_dst_pos, copy->cp_count);
-
-	if (bytes < 0)
-		status = nfserrno(bytes);
-	else {
-		copy->cp_res.wr_bytes_written = bytes;
-		copy->cp_res.wr_stable_how = NFS_UNSTABLE;
-		copy->cp_consecutive = 1;
-		copy->cp_synchronous = 1;
-		gen_boot_verifier(&copy->cp_res.wr_verifier, SVC_NET(rqstp));
-		status = nfs_ok;
+	copy->cp_clp = cstate->clp;
+	memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+		sizeof(struct knfsd_fh));
+	copy->net = SVC_NET(rqstp);
+	if (!copy->cp_synchronous) {
+		struct nfsd4_copy *async_copy;
+
+		status = nfsd4_init_copy_res(copy, 0);
+		async_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+		if (!async_copy) {
+			status = nfserrno(-ENOMEM);
+			goto out;
+		}
+		dup_copy_fields(copy, async_copy);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+			sizeof(copy->cp_dst_stateid));
+		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+		queue_work(copy_wq, &async_copy->cp_work);
+	} else {
+		status = nfsd4_do_copy(copy, 1);
	}
fs/nfsd/nfs4proc.c +0 -3
-
-	fput(src);
-	fput(dst);

out:

	return status;
}
fs/nfsd/nfs4state.c +7 -1
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net *net)
 		goto out_free_laundry;
 
 	set_max_delegations();
-	return 0;
 
+	ret = nfsd4_create_copy_queue();
+	if (ret)
+		goto out_free_callback;
+
+	return 0;
+out_free_callback:
+	nfsd4_destroy_callback_queue();

out_free_laundry:

	destroy_workqueue(laundry_wq);

out_cleanup_cred:

fs/nfsd/nfs4state.c +1 -0
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net *net)
 	destroy_workqueue(laundry_wq);
 	nfsd4_destroy_callback_queue();
 	cleanup_callback_cred();
+	nfsd4_destroy_copy_queue();
 }
 

static void

fs/nfsd/state.h +2 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 							struct nfsd_net *nn);
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
fs/nfsd/xdr4.h +7 -0
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
 	struct nfsd4_callback	cp_cb;
 	__be32			nfserr;
 	struct knfsd_fh		fh;
+
+	struct work_struct      cp_work;
+	struct nfs4_client      *cp_clp;
+
+	struct file             *fh_src;
+	struct file             *fh_dst;
+	struct net              *net;
 };
 
 struct nfsd4_seek {
Signature · 1 line
1.8.3.1

A message that is not in the archive (sent off-list or not yet received): [email protected]

#14 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

Quoted 39 lines · Olga Kornievskaia
Quoted 28 lines · J. Bruce Fields
Quoted 16 lines · Olga Kornievskaia
Quoted 16 lines · J. Bruce Fields
Quoted 8 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.

Quoted 7 lines
Quoted 3 lines

The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case.

One reason is to allow for the copy to be cancelled in the “middle”. Otherwise, we have no control over once we call into the vfs_copy_file_range to stop the on-going copy. At least this way we check every 4MB chunks.

Quoted 7 lines

Yes, see my other email, I think we should signal the thread to stop it.

Even with copying in chunks--we'd like to be able to cancel the copy mid-chunk.

Talking this over here with Jeff and Trond.... I don't *think* there's an easy way to cancel a long-running work item.

Yes I believe I was looking into signaling but couldn’t figure out how to do it with the current code...

Quoted 7 lines
Quoted 3 lines

So probably you want to create your own thread for the copy instead. It looks like kthread_create/kthread_stop/kthread_should_stop are what you want? You can see some examples if you "git grep kthread net/sunrpc”.

Ok so should I instead create a dedicated thread to do all copies and have a way to give this thread work? Or are you ok with creating a thread for each individual copy which would be much simpler?

I'd be in favor of a thread per copy. That should make the cancelling either as well.

--b.

#15 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

On Thu, Sep 28, 2017 at 3:07 PM, J. Bruce Fields <[email protected]> wrote= :

Quoted 3 lines · Olga Kornievskaia
Quoted 2 lines
Quoted 2 lines

On Sep 28, 2017, at 2:55 PM, J. Bruce Fields <[email protected]> wrot=

e:

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines · Olga Kornievskaia
Quoted 2 lines
Quoted 2 lines

On Sep 28, 2017, at 2:07 PM, J. Bruce Fields <[email protected]> wr=

ote:

Quoted 11 lines
Quoted 11 lines
Quoted 11 lines
Quoted 11 lines
Quoted 11 lines · Olga Kornievskaia
Quoted 8 lines

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 6 lines

I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.

The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case.

One reason is to allow for the copy to be cancelled in the =E2=80=9Cm=

iddle=E2=80=9D.

Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

Otherwise, we have no control over once we call into the vfs_copy_fil=

e_range

Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

to stop the on-going copy. At least this way we check every 4MB chunk=

s.

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

Yes, see my other email, I think we should signal the thread to stop i=

t.

Quoted 8 lines
Quoted 8 lines
Quoted 6 lines

Even with copying in chunks--we'd like to be able to cancel the copy mid-chunk.

Talking this over here with Jeff and Trond.... I don't *think* there's an easy way to cancel a long-running work item.

Yes I believe I was looking into signaling but couldn=E2=80=99t figure o=

ut how to do it

Quoted 3 lines
Quoted 3 lines

with the current code...

Quoted 1 line

So probably you want to create your own thread for the copy instead. =

It

Quoted 1 line
Quoted 1 line
Quoted 1 line

looks like kthread_create/kthread_stop/kthread_should_stop are what yo=

u

Quoted 1 line
Quoted 1 line
Quoted 1 line

want? You can see some examples if you "git grep kthread net/sunrpc=

=E2=80=9D.

Quoted 2 lines
Quoted 2 lines

Ok so should I instead create a dedicated thread to do all copies and ha=

ve

Quoted 5 lines
Quoted 2 lines

a way to give this thread work? Or are you ok with creating a thread for each individual copy which would be much simpler?

I'd be in favor of a thread per copy. That should make the cancelling either as well.

ok thanks. will work on that.

#16 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

In reply to J. Bruce Fields
Quoted 7 lines · J. Bruce Fields
Quoted 6 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

Quoted 8 lines
Quoted 2 lines

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.

The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case.

Before I remove the loop, the other reason I kept the loop was for a meaningful OFFLOAD_STATUS. Without the loop, any query for the OFFLOAD_STATUS would either return 0 or if it's really really really really lucky then it might return full-bytes copy value. There will never be an intermediate reply. I kept the loop and kept track of bytes copied so far for the status query.

Quoted 276 lines

--b.

Quoted 267 lines

In case a failure
happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results.

---
 fs/nfsd/nfs4proc.c  | 164 ++++++++++++++++++++++++++++++++++++++++++++++------
 fs/nfsd/nfs4state.c |   9 ++-
 fs/nfsd/state.h     |   2 +
 fs/nfsd/xdr4.h      |   7 +++
 4 files changed, 162 insertions(+), 20 deletions(-)
fs/nfsd/nfs4proc.c +145 -19
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif
 
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+     copy_wq = create_singlethread_workqueue("nfsd4_copy");
+     if (!copy_wq)
+             return -ENOMEM;
+     return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
+{
+     destroy_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY             NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 out:
      return status;
 }
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+     struct nfsd4_copy *copy = container_of(cb, struct nfsd4_copy, cp_cb);
+
+     kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+                              struct rpc_task *task)
+{
+     return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops = {
+     .release = nfsd4_cb_offload_release,
+     .done = nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+     memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+             sizeof(copy->cp_dst_stateid));
+     copy->cp_res.wr_stable_how = NFS_UNSTABLE;
+     copy->cp_consecutive = 1;
+     copy->cp_synchronous = sync;
+     gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+     return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+     ssize_t bytes_copied = 0;
+     size_t bytes_total = copy->cp_count;
+     size_t bytes_to_copy;
+     u64 src_pos = copy->cp_src_pos;
+     u64 dst_pos = copy->cp_dst_pos;
+
+     do {
+             bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
+             bytes_copied = nfsd_copy_file_range(copy->fh_src, src_pos,
+                                     copy->fh_dst, dst_pos, bytes_to_copy);
+             if (bytes_copied <= 0)
+                     break;
+             bytes_total -= bytes_copied;
+             copy->cp_res.wr_bytes_written += bytes_copied;
+             src_pos += bytes_copied;
+             dst_pos += bytes_copied;
+     } while (bytes_total > 0 && !copy->cp_synchronous);
+     return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+     __be32 status;
+     ssize_t bytes;
+
+     bytes = _nfsd_copy_file_range(copy);
+     if (bytes < 0)
+             status = nfserrno(bytes);
+     else
+             status = nfsd4_init_copy_res(copy, sync);
+
+     fput(copy->fh_src);
+     fput(copy->fh_dst);
+     return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
+{
+     memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, sizeof(stateid_t));
+     memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, sizeof(stateid_t));
+     dst->cp_src_pos = src->cp_src_pos;
+     dst->cp_dst_pos = src->cp_dst_pos;
+     dst->cp_count = src->cp_count;
+     dst->cp_consecutive = src->cp_consecutive;
+     dst->cp_synchronous = src->cp_synchronous;
+     memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+     /* skipping nfsd4_callback */
+     memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+     dst->fh = src->fh;
+     dst->cp_clp = src->cp_clp;
+     dst->fh_src = src->fh_src;
+     dst->fh_dst = src->fh_dst;
+     dst->net = src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+     struct nfsd4_copy *copy =
+             container_of(work, struct nfsd4_copy, cp_work);
+     struct nfsd4_copy *cb_copy;
+
+     copy->nfserr = nfsd4_do_copy(copy, 0);
+     cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+     if (!cb_copy)
+             goto out;
+     memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+     cb_copy->cp_clp = copy->cp_clp;
+     cb_copy->nfserr = copy->nfserr;
+     memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+     nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+                     &nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
+     nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+     kfree(copy);
+}
 
 static __be32
 nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
              union nfsd4_op_u *u)
 {
      struct nfsd4_copy *copy = &u->copy;
-     struct file *src, *dst;
      __be32 status;
-     ssize_t bytes;
 
-     status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid, &src,
-                                &copy->cp_dst_stateid, &dst);
+     status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
+                                &copy->fh_src, &copy->cp_dst_stateid,
+                                &copy->fh_dst);
      if (status)
              goto out;
 
-     bytes = nfsd_copy_file_range(src, copy->cp_src_pos,
-                     dst, copy->cp_dst_pos, copy->cp_count);
-
-     if (bytes < 0)
-             status = nfserrno(bytes);
-     else {
-             copy->cp_res.wr_bytes_written = bytes;
-             copy->cp_res.wr_stable_how = NFS_UNSTABLE;
-             copy->cp_consecutive = 1;
-             copy->cp_synchronous = 1;
-             gen_boot_verifier(&copy->cp_res.wr_verifier, SVC_NET(rqstp));
-             status = nfs_ok;
+     copy->cp_clp = cstate->clp;
+     memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+             sizeof(struct knfsd_fh));
+     copy->net = SVC_NET(rqstp);
+     if (!copy->cp_synchronous) {
+             struct nfsd4_copy *async_copy;
+
+             status = nfsd4_init_copy_res(copy, 0);
+             async_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+             if (!async_copy) {
+                     status = nfserrno(-ENOMEM);
+                     goto out;
+             }
+             dup_copy_fields(copy, async_copy);
+             memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+                     sizeof(copy->cp_dst_stateid));
+             INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+             queue_work(copy_wq, &async_copy->cp_work);
+     } else {
+             status = nfsd4_do_copy(copy, 1);
      }
-
-     fput(src);
-     fput(dst);
 out:
      return status;
 }
fs/nfsd/nfs4state.c +8 -1
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net *net)
              goto out_free_laundry;
 
      set_max_delegations();
-     return 0;
 
+     ret = nfsd4_create_copy_queue();
+     if (ret)
+             goto out_free_callback;
+
+     return 0;
+out_free_callback:
+     nfsd4_destroy_callback_queue();
 out_free_laundry:
      destroy_workqueue(laundry_wq);
 out_cleanup_cred:
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net *net)
      destroy_workqueue(laundry_wq);
      nfsd4_destroy_callback_queue();
      cleanup_callback_cred();
+     nfsd4_destroy_copy_queue();
 }
 
 static void
fs/nfsd/state.h +2 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
                                                      struct nfsd_net *nn);
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
fs/nfsd/xdr4.h +7 -1
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
      struct nfsd4_callback   cp_cb;
      __be32                  nfserr;
      struct knfsd_fh         fh;
+
+     struct work_struct      cp_work;
+     struct nfs4_client      *cp_clp;
+
+     struct file             *fh_src;
+     struct file             *fh_dst;
+     struct net              *net;
 };
 
 struct nfsd4_seek {
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#17 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

Quoted 17 lines · Olga Kornievskaia
Quoted 7 lines · J. Bruce Fields
Quoted 6 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy. Then queue work and reply back to the client indicating copy is asynchronous.

Quoted 8 lines
Quoted 2 lines

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy.

I don't think you need to do this--just call vfs_copy_file_range() directly, as nfsd_copy_file_range does.

The 4MB chunking was only there to prevent a synchronous copy from taking too long, which isn't an issue in the async case.

Quoted 6 lines

Before I remove the loop, the other reason I kept the loop was for a meaningful OFFLOAD_STATUS. Without the loop, any query for the OFFLOAD_STATUS would either return 0 or if it's really really really really lucky then it might return full-bytes copy value. There will never be an intermediate reply. I kept the loop and kept track of bytes copied so far for the status query.

Ugh, I hadn't thought about OFFLOAD_STATUS.

Maybe I'm being ornery, but: would returning 0 be OK for a first pass? The client doesn't use OFFLOAD_STATUS anyway, does it? It'd require new userspace API.

One advantage of the current synchronous copy is that existing applications should still be able to track progress, since we return all the way back to the client after each 4MB chunk. We'll lose that with asynchronous copy, whether or not the server supports OFFLOAD_STATUS.

Anyway, if there's actually a chance that someone might use OFFLOAD_STATUS, then maybe we do want to keep that loop. I don't have a better suggestion.

--b.

Quoted 277 lines
Quoted 277 lines

--b.

Quoted 267 lines

In case a failure
happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results.

---
 fs/nfsd/nfs4proc.c  | 164 ++++++++++++++++++++++++++++++++++++++++++++++------
 fs/nfsd/nfs4state.c |   9 ++-
 fs/nfsd/state.h     |   2 +
 fs/nfsd/xdr4.h      |   7 +++
 4 files changed, 162 insertions(+), 20 deletions(-)
fs/nfsd/nfs4proc.c +145 -19
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif
 
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+     copy_wq = create_singlethread_workqueue("nfsd4_copy");
+     if (!copy_wq)
+             return -ENOMEM;
+     return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
+{
+     destroy_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY             NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 out:
      return status;
 }
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+     struct nfsd4_copy *copy = container_of(cb, struct nfsd4_copy, cp_cb);
+
+     kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+                              struct rpc_task *task)
+{
+     return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops = {
+     .release = nfsd4_cb_offload_release,
+     .done = nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+     memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+             sizeof(copy->cp_dst_stateid));
+     copy->cp_res.wr_stable_how = NFS_UNSTABLE;
+     copy->cp_consecutive = 1;
+     copy->cp_synchronous = sync;
+     gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+     return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+     ssize_t bytes_copied = 0;
+     size_t bytes_total = copy->cp_count;
+     size_t bytes_to_copy;
+     u64 src_pos = copy->cp_src_pos;
+     u64 dst_pos = copy->cp_dst_pos;
+
+     do {
+             bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
+             bytes_copied = nfsd_copy_file_range(copy->fh_src, src_pos,
+                                     copy->fh_dst, dst_pos, bytes_to_copy);
+             if (bytes_copied <= 0)
+                     break;
+             bytes_total -= bytes_copied;
+             copy->cp_res.wr_bytes_written += bytes_copied;
+             src_pos += bytes_copied;
+             dst_pos += bytes_copied;
+     } while (bytes_total > 0 && !copy->cp_synchronous);
+     return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+     __be32 status;
+     ssize_t bytes;
+
+     bytes = _nfsd_copy_file_range(copy);
+     if (bytes < 0)
+             status = nfserrno(bytes);
+     else
+             status = nfsd4_init_copy_res(copy, sync);
+
+     fput(copy->fh_src);
+     fput(copy->fh_dst);
+     return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
+{
+     memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, sizeof(stateid_t));
+     memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, sizeof(stateid_t));
+     dst->cp_src_pos = src->cp_src_pos;
+     dst->cp_dst_pos = src->cp_dst_pos;
+     dst->cp_count = src->cp_count;
+     dst->cp_consecutive = src->cp_consecutive;
+     dst->cp_synchronous = src->cp_synchronous;
+     memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+     /* skipping nfsd4_callback */
+     memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+     dst->fh = src->fh;
+     dst->cp_clp = src->cp_clp;
+     dst->fh_src = src->fh_src;
+     dst->fh_dst = src->fh_dst;
+     dst->net = src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+     struct nfsd4_copy *copy =
+             container_of(work, struct nfsd4_copy, cp_work);
+     struct nfsd4_copy *cb_copy;
+
+     copy->nfserr = nfsd4_do_copy(copy, 0);
+     cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+     if (!cb_copy)
+             goto out;
+     memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+     cb_copy->cp_clp = copy->cp_clp;
+     cb_copy->nfserr = copy->nfserr;
+     memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+     nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+                     &nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
+     nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+     kfree(copy);
+}
 
 static __be32
 nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
              union nfsd4_op_u *u)
 {
      struct nfsd4_copy *copy = &u->copy;
-     struct file *src, *dst;
      __be32 status;
-     ssize_t bytes;
 
-     status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid, &src,
-                                &copy->cp_dst_stateid, &dst);
+     status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
+                                &copy->fh_src, &copy->cp_dst_stateid,
+                                &copy->fh_dst);
      if (status)
              goto out;
 
-     bytes = nfsd_copy_file_range(src, copy->cp_src_pos,
-                     dst, copy->cp_dst_pos, copy->cp_count);
-
-     if (bytes < 0)
-             status = nfserrno(bytes);
-     else {
-             copy->cp_res.wr_bytes_written = bytes;
-             copy->cp_res.wr_stable_how = NFS_UNSTABLE;
-             copy->cp_consecutive = 1;
-             copy->cp_synchronous = 1;
-             gen_boot_verifier(&copy->cp_res.wr_verifier, SVC_NET(rqstp));
-             status = nfs_ok;
+     copy->cp_clp = cstate->clp;
+     memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+             sizeof(struct knfsd_fh));
+     copy->net = SVC_NET(rqstp);
+     if (!copy->cp_synchronous) {
+             struct nfsd4_copy *async_copy;
+
+             status = nfsd4_init_copy_res(copy, 0);
+             async_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+             if (!async_copy) {
+                     status = nfserrno(-ENOMEM);
+                     goto out;
+             }
+             dup_copy_fields(copy, async_copy);
+             memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+                     sizeof(copy->cp_dst_stateid));
+             INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+             queue_work(copy_wq, &async_copy->cp_work);
+     } else {
+             status = nfsd4_do_copy(copy, 1);
      }
-
-     fput(src);
-     fput(dst);
 out:
      return status;
 }
fs/nfsd/nfs4state.c +8 -1
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net *net)
              goto out_free_laundry;
 
      set_max_delegations();
-     return 0;
 
+     ret = nfsd4_create_copy_queue();
+     if (ret)
+             goto out_free_callback;
+
+     return 0;
+out_free_callback:
+     nfsd4_destroy_callback_queue();
 out_free_laundry:
      destroy_workqueue(laundry_wq);
 out_cleanup_cred:
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net *net)
      destroy_workqueue(laundry_wq);
      nfsd4_destroy_callback_queue();
      cleanup_callback_cred();
+     nfsd4_destroy_copy_queue();
 }
 
 static void
fs/nfsd/state.h +2 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
                                                      struct nfsd_net *nn);
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
fs/nfsd/xdr4.h +7 -1
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
      struct nfsd4_callback   cp_cb;
      __be32                  nfserr;
      struct knfsd_fh         fh;
+
+     struct work_struct      cp_work;
+     struct nfs4_client      *cp_clp;
+
+     struct file             *fh_src;
+     struct file             *fh_dst;
+     struct net              *net;
 };
 
 struct nfsd4_seek {
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#18 lore raw

Re: [PATCH v4 05/10] NFSD first draft of async copy

In reply to Olga Kornievskaia
Quoted 3 lines · Olga Kornievskaia

Asynchronous copies are handled by a single threaded workqueue. If we get asynchronous request, make sure to copy the needed arguments/state from the stack before starting the copy.

I'm worried by those copies--I suspect you want to be taking actual references (so, bumping reference counts) to the clp and the filehandles, not just copying pointers.

--b.

Quoted 274 lines

Then
queue work and reply back to the client indicating copy is asynchronous.

nfsd_copy_file_range() will copy in 4MBchunk so do a loop over the total number of bytes need to copy. In case a failure happens in the middle, we can return an error as well as how much we copied so far. Once done creating a workitem for the callback workqueue and send CB_OFFLOAD with the results.

---
 fs/nfsd/nfs4proc.c  | 164 ++++++++++++++++++++++++++++++++++++++++++++++------
 fs/nfsd/nfs4state.c |   9 ++-
 fs/nfsd/state.h     |   2 +
 fs/nfsd/xdr4.h      |   7 +++
 4 files changed, 162 insertions(+), 20 deletions(-)
fs/nfsd/nfs4proc.c +145 -19
index 11ade90..de21b1a 100644
@@ -76,6 +76,21 @@
 { }
 #endif
 
+static struct workqueue_struct *copy_wq;
+
+int nfsd4_create_copy_queue(void)
+{
+	copy_wq = create_singlethread_workqueue("nfsd4_copy");
+	if (!copy_wq)
+		return -ENOMEM;
+	return 0;
+}
+
+void nfsd4_destroy_copy_queue(void)
+{
+	destroy_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1085,37 +1100,148 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 out:
 	return status;
 }
+static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
+{
+	struct nfsd4_copy *copy = container_of(cb, struct nfsd4_copy, cp_cb);
+
+	kfree(copy);
+}
+
+static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
+				 struct rpc_task *task)
+{
+	return 1;
+}
+
+static const struct nfsd4_callback_ops nfsd4_cb_offload_ops = {
+	.release = nfsd4_cb_offload_release,
+	.done = nfsd4_cb_offload_done
+};
+
+static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
+{
+	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+		sizeof(copy->cp_dst_stateid));
+	copy->cp_res.wr_stable_how = NFS_UNSTABLE;
+	copy->cp_consecutive = 1;
+	copy->cp_synchronous = sync;
+	gen_boot_verifier(&copy->cp_res.wr_verifier, copy->net);
+
+	return nfs_ok;
+}
+
+static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
+{
+	ssize_t bytes_copied = 0;
+	size_t bytes_total = copy->cp_count;
+	size_t bytes_to_copy;
+	u64 src_pos = copy->cp_src_pos;
+	u64 dst_pos = copy->cp_dst_pos;
+
+	do {
+		bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
+		bytes_copied = nfsd_copy_file_range(copy->fh_src, src_pos,
+					copy->fh_dst, dst_pos, bytes_to_copy);
+		if (bytes_copied <= 0)
+			break;
+		bytes_total -= bytes_copied;
+		copy->cp_res.wr_bytes_written += bytes_copied;
+		src_pos += bytes_copied;
+		dst_pos += bytes_copied;
+	} while (bytes_total > 0 && !copy->cp_synchronous);
+	return bytes_copied;
+}
+
+static int nfsd4_do_copy(struct nfsd4_copy *copy, bool sync)
+{
+	__be32 status;
+	ssize_t bytes;
+
+	bytes = _nfsd_copy_file_range(copy);
+	if (bytes < 0)
+		status = nfserrno(bytes);
+	else
+		status = nfsd4_init_copy_res(copy, sync);
+
+	fput(copy->fh_src);
+	fput(copy->fh_dst);
+	return status;
+}
+
+static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
+{
+	memcpy(&dst->cp_src_stateid, &src->cp_src_stateid, sizeof(stateid_t));
+	memcpy(&dst->cp_dst_stateid, &src->cp_dst_stateid, sizeof(stateid_t));
+	dst->cp_src_pos = src->cp_src_pos;
+	dst->cp_dst_pos = src->cp_dst_pos;
+	dst->cp_count = src->cp_count;
+	dst->cp_consecutive = src->cp_consecutive;
+	dst->cp_synchronous = src->cp_synchronous;
+	memcpy(&dst->cp_res, &src->cp_res, sizeof(src->cp_res));
+	/* skipping nfsd4_callback */
+	memcpy(&dst->fh, &src->fh, sizeof(src->fh));
+	dst->fh = src->fh;
+	dst->cp_clp = src->cp_clp;
+	dst->fh_src = src->fh_src;
+	dst->fh_dst = src->fh_dst;
+	dst->net = src->net;
+}
+
+static void nfsd4_do_async_copy(struct work_struct *work)
+{
+	struct nfsd4_copy *copy =
+		container_of(work, struct nfsd4_copy, cp_work);
+	struct nfsd4_copy *cb_copy;
+
+	copy->nfserr = nfsd4_do_copy(copy, 0);
+	cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+	if (!cb_copy)
+		goto out;
+	memcpy(&cb_copy->cp_res, &copy->cp_res, sizeof(copy->cp_res));
+	cb_copy->cp_clp = copy->cp_clp;
+	cb_copy->nfserr = copy->nfserr;
+	memcpy(&cb_copy->fh, &copy->fh, sizeof(copy->fh));
+	nfsd4_init_cb(&cb_copy->cp_cb, cb_copy->cp_clp,
+			&nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
+	nfsd4_run_cb(&cb_copy->cp_cb);
+out:
+	kfree(copy);
+}
 
 static __be32
 nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		union nfsd4_op_u *u)
 {
 	struct nfsd4_copy *copy = &u->copy;
-	struct file *src, *dst;
 	__be32 status;
-	ssize_t bytes;
 
-	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid, &src,
-				   &copy->cp_dst_stateid, &dst);
+	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
+				   &copy->fh_src, &copy->cp_dst_stateid,
+				   &copy->fh_dst);
 	if (status)
 		goto out;
 
-	bytes = nfsd_copy_file_range(src, copy->cp_src_pos,
-			dst, copy->cp_dst_pos, copy->cp_count);
-
-	if (bytes < 0)
-		status = nfserrno(bytes);
-	else {
-		copy->cp_res.wr_bytes_written = bytes;
-		copy->cp_res.wr_stable_how = NFS_UNSTABLE;
-		copy->cp_consecutive = 1;
-		copy->cp_synchronous = 1;
-		gen_boot_verifier(&copy->cp_res.wr_verifier, SVC_NET(rqstp));
-		status = nfs_ok;
+	copy->cp_clp = cstate->clp;
+	memcpy(&copy->fh, &cstate->current_fh.fh_handle,
+		sizeof(struct knfsd_fh));
+	copy->net = SVC_NET(rqstp);
+	if (!copy->cp_synchronous) {
+		struct nfsd4_copy *async_copy;
+
+		status = nfsd4_init_copy_res(copy, 0);
+		async_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
+		if (!async_copy) {
+			status = nfserrno(-ENOMEM);
+			goto out;
+		}
+		dup_copy_fields(copy, async_copy);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
+			sizeof(copy->cp_dst_stateid));
+		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
+		queue_work(copy_wq, &async_copy->cp_work);
+	} else {
+		status = nfsd4_do_copy(copy, 1);
 	}
-
-	fput(src);
-	fput(dst);
 out:
 	return status;
 }
fs/nfsd/nfs4state.c +8 -1
index 0c04f81..fe6f275 100644
@@ -7033,8 +7033,14 @@ static int nfs4_state_create_net(struct net *net)
 		goto out_free_laundry;
 
 	set_max_delegations();
-	return 0;
 
+	ret = nfsd4_create_copy_queue();
+	if (ret)
+		goto out_free_callback;
+
+	return 0;
+out_free_callback:
+	nfsd4_destroy_callback_queue();
 out_free_laundry:
 	destroy_workqueue(laundry_wq);
 out_cleanup_cred:
@@ -7097,6 +7103,7 @@ static int nfs4_state_create_net(struct net *net)
 	destroy_workqueue(laundry_wq);
 	nfsd4_destroy_callback_queue();
 	cleanup_callback_cred();
+	nfsd4_destroy_copy_queue();
 }
 
 static void
fs/nfsd/state.h +2 -0
index f8b0210..b5358cf 100644
@@ -630,6 +630,8 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 							struct nfsd_net *nn);
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
+extern int nfsd4_create_copy_queue(void);
+extern void nfsd4_destroy_copy_queue(void);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
fs/nfsd/xdr4.h +7 -0
index 9b0c099..f7a94d3 100644
@@ -529,6 +529,13 @@ struct nfsd4_copy {
 	struct nfsd4_callback	cp_cb;
 	__be32			nfserr;
 	struct knfsd_fh		fh;
+
+	struct work_struct      cp_work;
+	struct nfs4_client      *cp_clp;
+
+	struct file             *fh_src;
+	struct file             *fh_dst;
+	struct net              *net;
 };
 
 struct nfsd4_seek {
Signature · 1 line
1.8.3.1
#19 lore raw

PATCH v4 6/10 NFSD return nfs4_stid in nfs4_preprocess_stateid_op

In reply to Olga Kornievskaia

Needed for copy to add nfs4_cp_state to the nfs4_stid.

---
 fs/nfsd/nfs4proc.c  | 17 ++++++++++-------
 fs/nfsd/nfs4state.c |  8 ++++++--
 fs/nfsd/state.h     |  3 ++-
 3 files changed, 18 insertions(+), 10 deletions(-)
fs/nfsd/nfs4proc.c +10 -7
index de21b1a..044af6b 100644
@@ -787,7 +787,8 @@ static __be32 nfsd4_do_lookupp(struct svc_rqst *rqstp, struct svc_fh *fh)
 	/* check stateid */
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
 					&read->rd_stateid, RD_STATE,
-					&read->rd_filp, &read->rd_tmp_file);
+					&read->rd_filp, &read->rd_tmp_file,
+					NULL);
 	if (status) {
 		dprintk("NFSD: nfsd4_read: couldn't process stateid!\n");
 		goto out;
@@ -953,7 +954,7 @@ static __be32 nfsd4_do_lookupp(struct svc_rqst *rqstp, struct svc_fh *fh)
 	if (setattr->sa_iattr.ia_valid & ATTR_SIZE) {
 		status = nfs4_preprocess_stateid_op(rqstp, cstate,
 				&cstate->current_fh, &setattr->sa_stateid,
-				WR_STATE, NULL, NULL);
+				WR_STATE, NULL, NULL, NULL);
 		if (status) {
 			dprintk("NFSD: nfsd4_setattr: couldn't process stateid!\n");
 			return status;
@@ -1019,7 +1020,7 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 		return nfserr_inval;
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
-						stateid, WR_STATE, &filp, NULL);
+					stateid, WR_STATE, &filp, NULL, NULL);
 	if (status) {
 		dprintk("NFSD: nfsd4_write: couldn't process stateid!\n");
 		return status;
@@ -1050,14 +1051,16 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 	__be32 status;
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->save_fh,
-					    src_stateid, RD_STATE, src, NULL);
+					    src_stateid, RD_STATE, src, NULL,
+					    NULL);
 	if (status) {
 		dprintk("NFSD: %s: couldn't process src stateid!\n", __func__);
 		goto out;
 	}
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
-					    dst_stateid, WR_STATE, dst, NULL);
+					    dst_stateid, WR_STATE, dst, NULL,
+					    NULL);
 	if (status) {
 		dprintk("NFSD: %s: couldn't process dst stateid!\n", __func__);
 		goto out_put_src;
@@ -1263,7 +1266,7 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
 					    &fallocate->falloc_stateid,
-					    WR_STATE, &file, NULL);
+					    WR_STATE, &file, NULL, NULL);
 	if (status != nfs_ok) {
 		dprintk("NFSD: nfsd4_fallocate: couldn't process stateid!\n");
 		return status;
@@ -1310,7 +1313,7 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
 					    &seek->seek_stateid,
-					    RD_STATE, &file, NULL);
+					    RD_STATE, &file, NULL, NULL);
 	if (status) {
 		dprintk("NFSD: nfsd4_seek: couldn't process stateid!\n");
 		return status;
fs/nfsd/nfs4state.c +6 -2
index fe6f275..2c89dae 100644
@@ -4945,7 +4945,8 @@ static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
 __be32
 nfs4_preprocess_stateid_op(struct svc_rqst *rqstp,
 		struct nfsd4_compound_state *cstate, struct svc_fh *fhp,
-		stateid_t *stateid, int flags, struct file **filpp, bool *tmp_file)
+		stateid_t *stateid, int flags, struct file **filpp,
+		bool *tmp_file, struct nfs4_stid **cstid)
 {
 	struct inode *ino = d_inode(fhp->fh_dentry);
 	struct net *net = SVC_NET(rqstp);
@@ -4996,8 +4997,11 @@ static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
 	if (!status && filpp)
 		status = nfs4_check_file(rqstp, fhp, s, filpp, tmp_file, flags);
 out:
-	if (s)
+	if (s) {
+		if (!status && cstid)
+			*cstid = s;
 		nfs4_put_stid(s);
+	}
 	return status;
 }
 
fs/nfsd/state.h +2 -1
index b5358cf..cfd13f0 100644
@@ -600,7 +600,8 @@ struct nfsd4_blocked_lock {
 
 extern __be32 nfs4_preprocess_stateid_op(struct svc_rqst *rqstp,
 		struct nfsd4_compound_state *cstate, struct svc_fh *fhp,
-		stateid_t *stateid, int flags, struct file **filp, bool *tmp_file);
+		stateid_t *stateid, int flags, struct file **filp,
+		bool *tmp_file, struct nfs4_stid **cstid);
 __be32 nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
 		     stateid_t *stateid, unsigned char typemask,
 		     struct nfs4_stid **s, struct nfsd_net *nn);
Signature · 1 line
1.8.3.1
#20 lore raw

PATCH v4 7/10 NFSD create new stateid for async copy

In reply to Olga Kornievskaia

Generate a new stateid to be used for reply to the asynchronous COPY (this would also be used later by COPY_NOTIFY as well). Associate the stateid with the parent OPEN/LOCK/DELEG stateid that can be freed during the free of the parent stateid. However, right now deciding to bind the lifetime to when the vfs copy is done. This way don't need to keep the nfsd_net structure for the callback. The drawback is that time copy state information is available for query by OFFLOAD_STATUS is slightly less.

---
 fs/nfsd/netns.h     |  8 +++++++
 fs/nfsd/nfs4proc.c  | 31 ++++++++++++++++++-------
 fs/nfsd/nfs4state.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfsd/nfsctl.c    |  1 +
 fs/nfsd/state.h     | 14 ++++++++++++
 fs/nfsd/xdr4.h      |  2 ++
 6 files changed, 114 insertions(+), 8 deletions(-)
fs/nfsd/netns.h +8 -0
index 3714231..2c88a95 100644
@@ -119,6 +119,14 @@ struct nfsd_net {
 	u32 clverifier_counter;
 
 	struct svc_serv *nfsd_serv;
+
+	/*
+	 * clientid and stateid data for construction of net unique COPY
+	 * stateids.
+	 */
+	u32		s2s_cp_cl_id;
+	struct idr	s2s_cp_stateids;
+	spinlock_t	s2s_cp_lock;
 };
 
 /* Simple check to find out if a given net was properly initialized */
fs/nfsd/nfs4proc.c +23 -8
index 044af6b..3cddebb 100644
@@ -1046,7 +1046,8 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 static __be32
 nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		  stateid_t *src_stateid, struct file **src,
-		  stateid_t *dst_stateid, struct file **dst)
+		  stateid_t *dst_stateid, struct file **dst,
+		  struct nfs4_stid **stid)
 {
 	__be32 status;
 
@@ -1060,7 +1061,7 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
 					    dst_stateid, WR_STATE, dst, NULL,
-					    NULL);
+					    stid);
 	if (status) {
 		dprintk("NFSD: %s: couldn't process dst stateid!\n", __func__);
 		goto out_put_src;
@@ -1091,7 +1092,7 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 	__be32 status;
 
 	status = nfsd4_verify_copy(rqstp, cstate, &clone->cl_src_stateid, &src,
-				   &clone->cl_dst_stateid, &dst);
+				   &clone->cl_dst_stateid, &dst, NULL);
 	if (status)
 		goto out;
 
@@ -1123,8 +1124,6 @@ static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
 
 static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
 {
-	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
-		sizeof(copy->cp_dst_stateid));
 	copy->cp_res.wr_stable_how = NFS_UNSTABLE;
 	copy->cp_consecutive = 1;
 	copy->cp_synchronous = sync;
@@ -1188,6 +1187,8 @@ static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
 	dst->fh_src = src->fh_src;
 	dst->fh_dst = src->fh_dst;
 	dst->net = src->net;
+	dst->stid = src->stid;
+	dst->cps = src->cps;
 }
 
 static void nfsd4_do_async_copy(struct work_struct *work)
@@ -1208,6 +1209,9 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 			&nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
 	nfsd4_run_cb(&cb_copy->cp_cb);
 out:
+	list_del(&copy->cps->cp_list);
+	nfs4_free_cp_state(copy->cps);
+	nfs4_put_stid(copy->stid);
 	kfree(copy);
 }
 
@@ -1220,7 +1224,7 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 
 	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
 				   &copy->fh_src, &copy->cp_dst_stateid,
-				   &copy->fh_dst);
+				   &copy->fh_dst, &copy->stid);
 	if (status)
 		goto out;
 
@@ -1229,6 +1233,7 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		sizeof(struct knfsd_fh));
 	copy->net = SVC_NET(rqstp);
 	if (!copy->cp_synchronous) {
+		struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
 		struct nfsd4_copy *async_copy;
 
 		status = nfsd4_init_copy_res(copy, 0);
@@ -1237,9 +1242,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 			status = nfserrno(-ENOMEM);
 			goto out;
 		}
+		copy->cps = nfs4_alloc_init_cp_state(nn, copy->stid);
+		if (!copy->cps) {
+			status = nfserrno(-ENOMEM);
+			kfree(async_copy);
+			goto out;
+		}
+		/* take a reference on the parent stateid so it's not
+		 * not freed by the copy compound
+		 */
+		atomic_inc(&copy->stid->sc_count);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cps->cp_stateid,
+			sizeof(copy->cps->cp_stateid));
 		dup_copy_fields(copy, async_copy);
-		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
-			sizeof(copy->cp_dst_stateid));
 		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
 		queue_work(copy_wq, &async_copy->cp_work);
 	} else {
fs/nfsd/nfs4state.c +66 -0

90 lines not on this page · raw message

fs/nfsd/nfsctl.c +1 -0
index 6493df6..232270d 100644
@@ -1241,6 +1241,7 @@ static __net_init int nfsd_init_net(struct net *net)
 	nn->nfsd4_grace = 90;
 	nn->clverifier_counter = prandom_u32();
 	nn->clientid_counter = prandom_u32();
+	nn->s2s_cp_cl_id = nn->clientid_counter++;
 	return 0;
 
 out_idmap_error:
fs/nfsd/state.h +14 -0
index cfd13f0..8724955 100644
@@ -93,6 +93,7 @@ struct nfs4_stid {
 #define NFS4_REVOKED_DELEG_STID 16
 #define NFS4_CLOSED_DELEG_STID 32
 #define NFS4_LAYOUT_STID 64
+	struct list_head	sc_cp_list;
 	unsigned char		sc_type;
 	stateid_t		sc_stateid;
 	spinlock_t		sc_lock;
@@ -102,6 +103,17 @@ struct nfs4_stid {
 };
 
 /*
+ * Keep a list of stateids issued by the COPY, associate it with the
+ * parent OPEN/LOCK/DELEG stateid. Used for lookup by
+ * OFFLOAD_CANCEL and OFFLOAD_STATUS (as well as COPY_NOTIFY)
+ */
+struct nfs4_cp_state {
+	stateid_t		cp_stateid;
+	struct list_head	cp_list;	/* per parent nfs4_stid */
+	struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+};
+
+/*
  * Represents a delegation stateid. The nfs4_client holds references to these
  * and they are put when it is being destroyed or when the delegation is
  * returned by the client:
@@ -607,6 +619,8 @@ __be32 nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
 		     struct nfs4_stid **s, struct nfsd_net *nn);
 struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *slab,
 				  void (*sc_free)(struct nfs4_stid *));
+struct nfs4_cp_state *nfs4_alloc_init_cp_state(struct nfsd_net *nn, struct nfs4_stid *p_stid);
+void nfs4_free_cp_state(struct nfs4_cp_state *cps);
 void nfs4_unhash_stid(struct nfs4_stid *s);
 void nfs4_put_stid(struct nfs4_stid *s);
 void nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid);
fs/nfsd/xdr4.h +2 -0
index f7a94d3..4ccd43b 100644
@@ -536,6 +536,8 @@ struct nfsd4_copy {
 	struct file             *fh_src;
 	struct file             *fh_dst;
 	struct net              *net;
+	struct nfs4_stid        *stid;
+	struct nfs4_cp_state	*cps;
 };
 
 struct nfsd4_seek {
Signature · 1 line
1.8.3.1
#21 lore raw

Re: [PATCH v4 07/10] NFSD create new stateid for async copy

Quoted 8 lines · Olga Kornievskaia

Generate a new stateid to be used for reply to the asynchronous COPY (this would also be used later by COPY_NOTIFY as well). Associate the stateid with the parent OPEN/LOCK/DELEG stateid that can be freed during the free of the parent stateid. However, right now deciding to bind the lifetime to when the vfs copy is done. This way don't need to keep the nfsd_net structure for the callback. The drawback is that time copy state information is available for query by OFFLOAD_STATUS is slightly less.

I don't think we're under any obligation to handle OFFLOAD_STATUS after the copy is done, are we?

There's a possibility that OFFLOAD_STATUS might arrive just as the copy finishes, and the client might even received the failed response to OFFLOAD_STATUS before it receives the final CB_OFFLOAD. I think it's up to the client to handle that situation.

--b.

Quoted 294 lines
---
 fs/nfsd/netns.h     |  8 +++++++
 fs/nfsd/nfs4proc.c  | 31 ++++++++++++++++++-------
 fs/nfsd/nfs4state.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/nfsd/nfsctl.c    |  1 +
 fs/nfsd/state.h     | 14 ++++++++++++
 fs/nfsd/xdr4.h      |  2 ++
 6 files changed, 114 insertions(+), 8 deletions(-)
fs/nfsd/netns.h +8 -0
index 3714231..2c88a95 100644
@@ -119,6 +119,14 @@ struct nfsd_net {
 	u32 clverifier_counter;
 
 	struct svc_serv *nfsd_serv;
+
+	/*
+	 * clientid and stateid data for construction of net unique COPY
+	 * stateids.
+	 */
+	u32		s2s_cp_cl_id;
+	struct idr	s2s_cp_stateids;
+	spinlock_t	s2s_cp_lock;
 };
 
 /* Simple check to find out if a given net was properly initialized */
fs/nfsd/nfs4proc.c +23 -8
index 044af6b..3cddebb 100644
@@ -1046,7 +1046,8 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 static __be32
 nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		  stateid_t *src_stateid, struct file **src,
-		  stateid_t *dst_stateid, struct file **dst)
+		  stateid_t *dst_stateid, struct file **dst,
+		  struct nfs4_stid **stid)
 {
 	__be32 status;
 
@@ -1060,7 +1061,7 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 
 	status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
 					    dst_stateid, WR_STATE, dst, NULL,
-					    NULL);
+					    stid);
 	if (status) {
 		dprintk("NFSD: %s: couldn't process dst stateid!\n", __func__);
 		goto out_put_src;
@@ -1091,7 +1092,7 @@ static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)
 	__be32 status;
 
 	status = nfsd4_verify_copy(rqstp, cstate, &clone->cl_src_stateid, &src,
-				   &clone->cl_dst_stateid, &dst);
+				   &clone->cl_dst_stateid, &dst, NULL);
 	if (status)
 		goto out;
 
@@ -1123,8 +1124,6 @@ static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
 
 static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
 {
-	memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
-		sizeof(copy->cp_dst_stateid));
 	copy->cp_res.wr_stable_how = NFS_UNSTABLE;
 	copy->cp_consecutive = 1;
 	copy->cp_synchronous = sync;
@@ -1188,6 +1187,8 @@ static void dup_copy_fields(struct nfsd4_copy *src, struct nfsd4_copy *dst)
 	dst->fh_src = src->fh_src;
 	dst->fh_dst = src->fh_dst;
 	dst->net = src->net;
+	dst->stid = src->stid;
+	dst->cps = src->cps;
 }
 
 static void nfsd4_do_async_copy(struct work_struct *work)
@@ -1208,6 +1209,9 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 			&nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
 	nfsd4_run_cb(&cb_copy->cp_cb);
 out:
+	list_del(&copy->cps->cp_list);
+	nfs4_free_cp_state(copy->cps);
+	nfs4_put_stid(copy->stid);
 	kfree(copy);
 }
 
@@ -1220,7 +1224,7 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 
 	status = nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
 				   &copy->fh_src, &copy->cp_dst_stateid,
-				   &copy->fh_dst);
+				   &copy->fh_dst, &copy->stid);
 	if (status)
 		goto out;
 
@@ -1229,6 +1233,7 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		sizeof(struct knfsd_fh));
 	copy->net = SVC_NET(rqstp);
 	if (!copy->cp_synchronous) {
+		struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
 		struct nfsd4_copy *async_copy;
 
 		status = nfsd4_init_copy_res(copy, 0);
@@ -1237,9 +1242,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 			status = nfserrno(-ENOMEM);
 			goto out;
 		}
+		copy->cps = nfs4_alloc_init_cp_state(nn, copy->stid);
+		if (!copy->cps) {
+			status = nfserrno(-ENOMEM);
+			kfree(async_copy);
+			goto out;
+		}
+		/* take a reference on the parent stateid so it's not
+		 * not freed by the copy compound
+		 */
+		atomic_inc(&copy->stid->sc_count);
+		memcpy(&copy->cp_res.cb_stateid, &copy->cps->cp_stateid,
+			sizeof(copy->cps->cp_stateid));
 		dup_copy_fields(copy, async_copy);
-		memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
-			sizeof(copy->cp_dst_stateid));
 		INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
 		queue_work(copy_wq, &async_copy->cp_work);
 	} else {
fs/nfsd/nfs4state.c +66 -0
index 2c89dae..be59baf 100644
@@ -658,6 +658,7 @@ struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *sla
 	/* Will be incremented before return to client: */
 	atomic_set(&stid->sc_count, 1);
 	spin_lock_init(&stid->sc_lock);
+	INIT_LIST_HEAD(&stid->sc_cp_list);
 
 	/*
 	 * It shouldn't be a problem to reuse an opaque stateid value.
@@ -674,6 +675,66 @@ struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *sla
 	return NULL;
 }
 
+/*
+ * Create a unique stateid_t to represent each COPY. Hang the copy
+ * stateids off the OPEN/LOCK/DELEG stateid from the client open
+ * the source file.
+ */
+struct nfs4_cp_state *nfs4_alloc_init_cp_state(struct nfsd_net *nn,
+					       struct nfs4_stid *p_stid)
+{
+	struct nfs4_cp_state *cps;
+	int new_id;
+
+	cps = kzalloc(sizeof(struct nfs4_cp_state), GFP_KERNEL);
+	if (!cps)
+		return NULL;
+	idr_preload(GFP_KERNEL);
+	spin_lock(&nn->s2s_cp_lock);
+	new_id = idr_alloc_cyclic(&nn->s2s_cp_stateids, cps, 0, 0, GFP_NOWAIT);
+	spin_unlock(&nn->s2s_cp_lock);
+	idr_preload_end();
+	if (new_id < 0)
+		goto out_free;
+	cps->cp_stateid.si_opaque.so_id = new_id;
+	cps->cp_stateid.si_opaque.so_clid.cl_boot = nn->boot_time;
+	cps->cp_stateid.si_opaque.so_clid.cl_id = nn->s2s_cp_cl_id;
+	cps->cp_p_stid = p_stid;
+	INIT_LIST_HEAD(&cps->cp_list);
+	list_add(&cps->cp_list, &p_stid->sc_cp_list);
+
+	return cps;
+out_free:
+	kfree(cps);
+	return NULL;
+}
+
+void nfs4_free_cp_state(struct nfs4_cp_state *cps)
+{
+	struct nfsd_net *nn;
+
+	nn = net_generic(cps->cp_p_stid->sc_client->net, nfsd_net_id);
+	spin_lock(&nn->s2s_cp_lock);
+	idr_remove(&nn->s2s_cp_stateids, cps->cp_stateid.si_opaque.so_id);
+	spin_unlock(&nn->s2s_cp_lock);
+
+	kfree(cps);
+}
+
+static void nfs4_free_cp_statelist(struct nfs4_stid *stid)
+{
+	struct nfs4_cp_state *cps;
+
+	might_sleep();
+
+	while (!list_empty(&stid->sc_cp_list)) {
+		cps = list_first_entry(&stid->sc_cp_list, struct nfs4_cp_state,
+				       cp_list);
+		list_del(&cps->cp_list);
+		nfs4_free_cp_state(cps);
+	}
+}
+
 static struct nfs4_ol_stateid * nfs4_alloc_open_stateid(struct nfs4_client *clp)
 {
 	struct nfs4_stid *stid;
@@ -819,6 +880,9 @@ static void block_delegations(struct knfsd_fh *fh)
 	}
 	idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
 	spin_unlock(&clp->cl_lock);
+
+	nfs4_free_cp_statelist(s);
+
 	s->sc_free(s);
 	if (fp)
 		put_nfs4_file(fp);
@@ -6952,6 +7016,8 @@ static int nfs4_state_create_net(struct net *net)
 	INIT_LIST_HEAD(&nn->close_lru);
 	INIT_LIST_HEAD(&nn->del_recall_lru);
 	spin_lock_init(&nn->client_lock);
+	spin_lock_init(&nn->s2s_cp_lock);
+	idr_init(&nn->s2s_cp_stateids);
 
 	spin_lock_init(&nn->blocked_locks_lock);
 	INIT_LIST_HEAD(&nn->blocked_locks_lru);
fs/nfsd/nfsctl.c +1 -0
index 6493df6..232270d 100644
@@ -1241,6 +1241,7 @@ static __net_init int nfsd_init_net(struct net *net)
 	nn->nfsd4_grace = 90;
 	nn->clverifier_counter = prandom_u32();
 	nn->clientid_counter = prandom_u32();
+	nn->s2s_cp_cl_id = nn->clientid_counter++;
 	return 0;
 
 out_idmap_error:
fs/nfsd/state.h +14 -0
index cfd13f0..8724955 100644
@@ -93,6 +93,7 @@ struct nfs4_stid {
 #define NFS4_REVOKED_DELEG_STID 16
 #define NFS4_CLOSED_DELEG_STID 32
 #define NFS4_LAYOUT_STID 64
+	struct list_head	sc_cp_list;
 	unsigned char		sc_type;
 	stateid_t		sc_stateid;
 	spinlock_t		sc_lock;
@@ -102,6 +103,17 @@ struct nfs4_stid {
 };
 
 /*
+ * Keep a list of stateids issued by the COPY, associate it with the
+ * parent OPEN/LOCK/DELEG stateid. Used for lookup by
+ * OFFLOAD_CANCEL and OFFLOAD_STATUS (as well as COPY_NOTIFY)
+ */
+struct nfs4_cp_state {
+	stateid_t		cp_stateid;
+	struct list_head	cp_list;	/* per parent nfs4_stid */
+	struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+};
+
+/*
  * Represents a delegation stateid. The nfs4_client holds references to these
  * and they are put when it is being destroyed or when the delegation is
  * returned by the client:
@@ -607,6 +619,8 @@ __be32 nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
 		     struct nfs4_stid **s, struct nfsd_net *nn);
 struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *slab,
 				  void (*sc_free)(struct nfs4_stid *));
+struct nfs4_cp_state *nfs4_alloc_init_cp_state(struct nfsd_net *nn, struct nfs4_stid *p_stid);
+void nfs4_free_cp_state(struct nfs4_cp_state *cps);
 void nfs4_unhash_stid(struct nfs4_stid *s);
 void nfs4_put_stid(struct nfs4_stid *s);
 void nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid);
fs/nfsd/xdr4.h +2 -0
index f7a94d3..4ccd43b 100644
@@ -536,6 +536,8 @@ struct nfsd4_copy {
 	struct file             *fh_src;
 	struct file             *fh_dst;
 	struct net              *net;
+	struct nfs4_stid        *stid;
+	struct nfs4_cp_state	*cps;
 };
 
 struct nfsd4_seek {
Signature · 1 line
1.8.3.1
#22 lore raw

Re: [PATCH v4 07/10] NFSD create new stateid for async copy

On Thu, Sep 28, 2017 at 3:12 PM, J. Bruce Fields <[email protected]> wrote= :

Quoted 10 lines · Olga Kornievskaia
Quoted 8 lines

Generate a new stateid to be used for reply to the asynchronous COPY (this would also be used later by COPY_NOTIFY as well). Associate the stateid with the parent OPEN/LOCK/DELEG stateid that can be freed during the free of the parent stateid. However, right now deciding to bind the lifetime to when the vfs copy is done. This way don't need to keep the nfsd_net structure for the callback. The drawback is that time copy state information is available for query by OFFLOAD_STATUS is slightly less.

Quoted 2 lines

I don't think we're under any obligation to handle OFFLOAD_STATUS after the copy is done, are we?

It depends on how you define =E2=80=9Ccopy is done=E2=80=9D. I=E2=80=99d sa= y copy is done once the
server sent the CB_OFFLOAD. But I=E2=80=99m choosing to clear the state onc= e the
vfs_copy_file_range() is done. Then the callback is queued up (what if there is wait until the callback can be processed) and there is a period where if the client were to send a OFFLOAD_STATUS it should get the bytes representing the full copy but instead the client will get an error b= ack.

I was worried about what if some implementation depend on querying for the status and stopping to poll only when they received that all bytes were copied and they might not get that with how the server is coded right now?

Thus I thought I=E2=80=99d bring it up to decide if the freeing should be m= oved into
the callback process (but then more stuff needed to be copied)=E2=80=A6.

Quoted 5 lines

There's a possibility that OFFLOAD_STATUS might arrive just as the copy finishes, and the client might even received the failed response to OFFLOAD_STATUS before it receives the final CB_OFFLOAD. I think it's up to the client to handle that situation.

Quoted 8 lines

--b.

Quoted 6 lines
---
 fs/nfsd/netns.h     |  8 +++++++
 fs/nfsd/nfs4proc.c  | 31 ++++++++++++++++++-------

fs/nfsd/nfs4state.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++=

+++++++

Quoted 23 lines
Quoted 23 lines
 fs/nfsd/nfsctl.c    |  1 +
 fs/nfsd/state.h     | 14 ++++++++++++
 fs/nfsd/xdr4.h      |  2 ++
 6 files changed, 114 insertions(+), 8 deletions(-)
fs/nfsd/netns.h +8 -0
index 3714231..2c88a95 100644
@@ -119,6 +119,14 @@ struct nfsd_net {
      u32 clverifier_counter;
 
      struct svc_serv *nfsd_serv;
+
+     /*
+      * clientid and stateid data for construction of net unique COPY
+      * stateids.
+      */
+     u32             s2s_cp_cl_id;
+     struct idr      s2s_cp_stateids;
+     spinlock_t      s2s_cp_lock;
 };
 
Quoted 6 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, =

Quoted 6 lines
fs/nfsd/netns.h +0 -0
 /* Simple check to find out if a given net was properly initialized */
fs/nfsd/nfs4proc.c +0 -0
index 044af6b..3cddebb 100644
@@ -1046,7 +1046,8 @@ static int fill_in_write_vector(struct kvec *vec, =

struct nfsd4_write *write)

Quoted 2 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, = · line 1047

Quoted 2 lines
fs/nfsd/nfs4proc.c +0 -0
 static __be32
 nfsd4_verify_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *=

cstate,

Quoted 8 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, =

Quoted 8 lines
fs/nfsd/nfs4proc.c +2 -1
                stateid_t *src_stateid, struct file **src,
-               stateid_t *dst_stateid, struct file **dst)
+               stateid_t *dst_stateid, struct file **dst,
+               struct nfs4_stid **stid)
 {
      __be32 status;
 
@@ -1060,7 +1061,7 @@ static int fill_in_write_vector(struct kvec *vec, =

struct nfsd4_write *write)

Quoted 2 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, = · line 1061

Quoted 2 lines
fs/nfsd/nfs4proc.c +0 -0
      status =3D nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->curr=

ent_fh,

Quoted 1 line

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, = · line 1062

Quoted 1 line
fs/nfsd/nfs4proc.c +0 -0
                                          dst_stateid, WR_STATE, dst, NU=

LL,

Quoted 4 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

Quoted 4 lines
fs/nfsd/nfs4proc.c +1 -1
-                                         NULL);
+                                         stid);
      if (status) {
              dprintk("NFSD: %s: couldn't process dst stateid!\n", __fun=

c__);

Quoted 2 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, =

Quoted 2 lines
fs/nfsd/nfs4proc.c +0 -0
              goto out_put_src;
@@ -1091,7 +1092,7 @@ static int fill_in_write_vector(struct kvec *vec, =

struct nfsd4_write *write)

Quoted 3 lines

fs/nfsd/nfs4proc.c static int fill_in_write_vector(struct kvec *vec, struct nfsd4_write *write)

Quoted 3 lines
fs/nfsd/nfs4proc.c +0 -0
      __be32 status;
 
      status =3D nfsd4_verify_copy(rqstp, cstate, &clone->cl_src_stateid=

, &src,

Quoted 6 lines

fs/nfsd/nfs4proc.c static int nfsd4_cb_offload_done(struct nfsd4_call=

Quoted 6 lines
fs/nfsd/nfs4proc.c +1 -1
-                                &clone->cl_dst_stateid, &dst);
+                                &clone->cl_dst_stateid, &dst, NULL);
      if (status)
              goto out;
 
@@ -1123,8 +1124,6 @@ static int nfsd4_cb_offload_done(struct nfsd4_call=

back *cb,

Quoted 9 lines

fs/nfsd/nfs4proc.c static void dup_copy_fields(struct nfsd4_copy *src=

Quoted 9 lines
fs/nfsd/nfs4proc.c +0 -2
 static int nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync)
 {
-     memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
-             sizeof(copy->cp_dst_stateid));
      copy->cp_res.wr_stable_how =3D NFS_UNSTABLE;
      copy->cp_consecutive =3D 1;
      copy->cp_synchronous =3D sync;
@@ -1188,6 +1187,8 @@ static void dup_copy_fields(struct nfsd4_copy *src=

, struct nfsd4_copy *dst)

Quoted 9 lines

fs/nfsd/nfs4proc.c static void nfsd4_do_async_copy(struct work_struct=

Quoted 9 lines
fs/nfsd/nfs4proc.c +2 -0
      dst->fh_src =3D src->fh_src;
      dst->fh_dst =3D src->fh_dst;
      dst->net =3D src->net;
+     dst->stid =3D src->stid;
+     dst->cps =3D src->cps;
 }
 
 static void nfsd4_do_async_copy(struct work_struct *work)
@@ -1208,6 +1209,9 @@ static void nfsd4_do_async_copy(struct work_struct=

*work)

Quoted 10 lines

fs/nfsd/nfs4proc.c static void nfsd4_do_async_copy(struct work_struct=

Quoted 10 lines
fs/nfsd/nfs4proc.c +3 -0
                      &nfsd4_cb_offload_ops, NFSPROC4_CLNT_CB_OFFLOAD);
      nfsd4_run_cb(&cb_copy->cp_cb);
 out:
+     list_del(&copy->cps->cp_list);
+     nfs4_free_cp_state(copy->cps);
+     nfs4_put_stid(copy->stid);
      kfree(copy);
 }
 
@@ -1220,7 +1224,7 @@ static void nfsd4_do_async_copy(struct work_struct=

*work)

Quoted 9 lines

fs/nfsd/nfs4proc.c static void nfsd4_do_async_copy(struct work_struct=

Quoted 9 lines
fs/nfsd/nfs4proc.c +1 -1
      status =3D nfsd4_verify_copy(rqstp, cstate, &copy->cp_src_stateid,
                                 &copy->fh_src, &copy->cp_dst_stateid,
-                                &copy->fh_dst);
+                                &copy->fh_dst, &copy->stid);
      if (status)
              goto out;
 
@@ -1229,6 +1233,7 @@ static void nfsd4_do_async_copy(struct work_struct=

*work)

Quoted 4 lines

fs/nfsd/nfs4proc.c static void nfsd4_do_async_copy(struct work_struct *work)

Quoted 4 lines
fs/nfsd/nfs4proc.c +1 -0
              sizeof(struct knfsd_fh));
      copy->net =3D SVC_NET(rqstp);
      if (!copy->cp_synchronous) {
+             struct nfsd_net *nn =3D net_generic(SVC_NET(rqstp), nfsd_n=

et_id);

Quoted 4 lines

fs/nfsd/nfs4proc.c static void nfsd4_do_async_copy(struct work_struc=

Quoted 4 lines
fs/nfsd/nfs4proc.c +0 -0
              struct nfsd4_copy *async_copy;
 
              status =3D nfsd4_init_copy_res(copy, 0);
@@ -1237,9 +1242,19 @@ static void nfsd4_do_async_copy(struct work_struc=

t *work)

Quoted 18 lines
Quoted 18 lines
fs/nfsd/nfs4proc.c +12 -2
                      status =3D nfserrno(-ENOMEM);
                      goto out;
              }
+             copy->cps =3D nfs4_alloc_init_cp_state(nn, copy->stid);
+             if (!copy->cps) {
+                     status =3D nfserrno(-ENOMEM);
+                     kfree(async_copy);
+                     goto out;
+             }
+             /* take a reference on the parent stateid so it's not
+              * not freed by the copy compound
+              */
+             atomic_inc(&copy->stid->sc_count);
+             memcpy(&copy->cp_res.cb_stateid, &copy->cps->cp_stateid,
+                     sizeof(copy->cps->cp_stateid));
              dup_copy_fields(copy, async_copy);
-             memcpy(&copy->cp_res.cb_stateid, &copy->cp_dst_stateid,
-                     sizeof(copy->cp_dst_stateid));
Quoted 8 lines

fs/nfsd/nfs4state.c struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client=

Quoted 8 lines
fs/nfsd/nfs4proc.c +0 -0
              INIT_WORK(&async_copy->cp_work, nfsd4_do_async_copy);
              queue_work(copy_wq, &async_copy->cp_work);
      } else {
fs/nfsd/nfs4state.c +0 -0
index 2c89dae..be59baf 100644
@@ -658,6 +658,7 @@ struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client=

*cl, struct kmem_cache *sla

Quoted 8 lines

fs/nfsd/nfs4state.c struct nfs4_stid *nfs4_alloc_stid(struct nfs4_clien=

Quoted 8 lines
fs/nfsd/nfs4state.c +1 -0
      /* Will be incremented before return to client: */
      atomic_set(&stid->sc_count, 1);
      spin_lock_init(&stid->sc_lock);
+     INIT_LIST_HEAD(&stid->sc_cp_list);
 
      /*
       * It shouldn't be a problem to reuse an opaque stateid value.
@@ -674,6 +675,66 @@ struct nfs4_stid *nfs4_alloc_stid(struct nfs4_clien=

t *cl, struct kmem_cache *sla

Quoted 12 lines
Quoted 12 lines
fs/nfsd/nfs4state.c +9 -0
      return NULL;
 }
 
+/*
+ * Create a unique stateid_t to represent each COPY. Hang the copy
+ * stateids off the OPEN/LOCK/DELEG stateid from the client open
+ * the source file.
+ */
+struct nfs4_cp_state *nfs4_alloc_init_cp_state(struct nfsd_net *nn,
+                                            struct nfs4_stid *p_stid)
+{
+     struct nfs4_cp_state *cps;
Quoted 8 lines

fs/nfsd/nfs4state.c struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *sla

Quoted 8 lines
fs/nfsd/nfs4state.c +8 -0
+     int new_id;
+
+     cps =3D kzalloc(sizeof(struct nfs4_cp_state), GFP_KERNEL);
+     if (!cps)
+             return NULL;
+     idr_preload(GFP_KERNEL);
+     spin_lock(&nn->s2s_cp_lock);
+     new_id =3D idr_alloc_cyclic(&nn->s2s_cp_stateids, cps, 0, 0, GFP_N=

OWAIT);

Quoted 29 lines
Quoted 29 lines
fs/nfsd/nfs4state.c +29 -0
+     spin_unlock(&nn->s2s_cp_lock);
+     idr_preload_end();
+     if (new_id < 0)
+             goto out_free;
+     cps->cp_stateid.si_opaque.so_id =3D new_id;
+     cps->cp_stateid.si_opaque.so_clid.cl_boot =3D nn->boot_time;
+     cps->cp_stateid.si_opaque.so_clid.cl_id =3D nn->s2s_cp_cl_id;
+     cps->cp_p_stid =3D p_stid;
+     INIT_LIST_HEAD(&cps->cp_list);
+     list_add(&cps->cp_list, &p_stid->sc_cp_list);
+
+     return cps;
+out_free:
+     kfree(cps);
+     return NULL;
+}
+
+void nfs4_free_cp_state(struct nfs4_cp_state *cps)
+{
+     struct nfsd_net *nn;
+
+     nn =3D net_generic(cps->cp_p_stid->sc_client->net, nfsd_net_id);
+     spin_lock(&nn->s2s_cp_lock);
+     idr_remove(&nn->s2s_cp_stateids, cps->cp_stateid.si_opaque.so_id);
+     spin_unlock(&nn->s2s_cp_lock);
+
+     kfree(cps);
+}
+
Quoted 8 lines

fs/nfsd/nfs4state.c struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *sla

Quoted 8 lines
fs/nfsd/nfs4state.c +8 -0
+static void nfs4_free_cp_statelist(struct nfs4_stid *stid)
+{
+     struct nfs4_cp_state *cps;
+
+     might_sleep();
+
+     while (!list_empty(&stid->sc_cp_list)) {
+             cps =3D list_first_entry(&stid->sc_cp_list, struct nfs4_cp=

_state,

Quoted 7 lines

fs/nfsd/nfs4state.c struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *sla

Quoted 7 lines
fs/nfsd/nfs4state.c +6 -0
+                                    cp_list);
+             list_del(&cps->cp_list);
+             nfs4_free_cp_state(cps);
+     }
+}
+
 static struct nfs4_ol_stateid * nfs4_alloc_open_stateid(struct nfs4_cli=

ent *clp)

Quoted 19 lines
Quoted 19 lines
fs/nfsd/nfs4state.c +5 -0
 {
      struct nfs4_stid *stid;
@@ -819,6 +880,9 @@ static void block_delegations(struct knfsd_fh *fh)
      }
      idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
      spin_unlock(&clp->cl_lock);
+
+     nfs4_free_cp_statelist(s);
+
      s->sc_free(s);
      if (fp)
              put_nfs4_file(fp);
@@ -6952,6 +7016,8 @@ static int nfs4_state_create_net(struct net *net)
      INIT_LIST_HEAD(&nn->close_lru);
      INIT_LIST_HEAD(&nn->del_recall_lru);
      spin_lock_init(&nn->client_lock);
+     spin_lock_init(&nn->s2s_cp_lock);
+     idr_init(&nn->s2s_cp_stateids);
 
Quoted 7 lines

fs/nfsd/nfsctl.c static __net_init int nfsd_init_net(struct net *ne=

Quoted 7 lines
fs/nfsd/nfs4state.c +0 -0
      spin_lock_init(&nn->blocked_locks_lock);
      INIT_LIST_HEAD(&nn->blocked_locks_lru);
fs/nfsd/nfsctl.c +0 -0
index 6493df6..232270d 100644
@@ -1241,6 +1241,7 @@ static __net_init int nfsd_init_net(struct net *ne=

t)

Quoted 22 lines
Quoted 22 lines
fs/nfsd/nfsctl.c +1 -0
      nn->nfsd4_grace =3D 90;
      nn->clverifier_counter =3D prandom_u32();
      nn->clientid_counter =3D prandom_u32();
+     nn->s2s_cp_cl_id =3D nn->clientid_counter++;
      return 0;
 
 out_idmap_error:
fs/nfsd/state.h +1 -0
index cfd13f0..8724955 100644
@@ -93,6 +93,7 @@ struct nfs4_stid {
 #define NFS4_REVOKED_DELEG_STID 16
 #define NFS4_CLOSED_DELEG_STID 32
 #define NFS4_LAYOUT_STID 64
+     struct list_head        sc_cp_list;
      unsigned char           sc_type;
      stateid_t               sc_stateid;
      spinlock_t              sc_lock;
@@ -102,6 +103,17 @@ struct nfs4_stid {
 };
 
Quoted 13 lines

fs/nfsd/state.h struct nfs4_stid {

Quoted 13 lines
fs/nfsd/state.h +11 -0
 /*
+ * Keep a list of stateids issued by the COPY, associate it with the
+ * parent OPEN/LOCK/DELEG stateid. Used for lookup by
+ * OFFLOAD_CANCEL and OFFLOAD_STATUS (as well as COPY_NOTIFY)
+ */
+struct nfs4_cp_state {
+     stateid_t               cp_stateid;
+     struct list_head        cp_list;        /* per parent nfs4_stid */
+     struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+};
+
+/*
  * Represents a delegation stateid. The nfs4_client holds references to=

these

Quoted 1 line

fs/nfsd/state.h struct nfs4_stid { · line 118

Quoted 1 line
fs/nfsd/state.h +0 -0
  * and they are put when it is being destroyed or when the delegation i=

s

Quoted 2 lines

fs/nfsd/state.h __be32 nfsd4_lookup_stateid(struct nfsd4_compound_st=

Quoted 2 lines
fs/nfsd/state.h +0 -0
  * returned by the client:
@@ -607,6 +619,8 @@ __be32 nfsd4_lookup_stateid(struct nfsd4_compound_st=

ate *cstate,

Quoted 2 lines

fs/nfsd/state.h __be32 nfsd4_lookup_stateid(struct nfsd4_compound_st= · line 620

Quoted 2 lines
fs/nfsd/state.h +0 -0
                   struct nfs4_stid **s, struct nfsd_net *nn);
 struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_c=

ache *slab,

Quoted 2 lines

fs/nfsd/state.h __be32 nfsd4_lookup_stateid(struct nfsd4_compound_st= · line 622

Quoted 2 lines
fs/nfsd/state.h +1 -0
                                void (*sc_free)(struct nfs4_stid *));
+struct nfs4_cp_state *nfs4_alloc_init_cp_state(struct nfsd_net *nn, str=

uct nfs4_stid *p_stid);

Quoted 24 lines
Quoted 18 lines
fs/nfsd/state.h +1 -0
+void nfs4_free_cp_state(struct nfs4_cp_state *cps);
 void nfs4_unhash_stid(struct nfs4_stid *s);
 void nfs4_put_stid(struct nfs4_stid *s);
 void nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid);
fs/nfsd/xdr4.h +2 -1
index f7a94d3..4ccd43b 100644
@@ -536,6 +536,8 @@ struct nfsd4_copy {
      struct file             *fh_src;
      struct file             *fh_dst;
      struct net              *net;
+     struct nfs4_stid        *stid;
+     struct nfs4_cp_state    *cps;
 };
 
 struct nfsd4_seek {
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#23 lore raw

Re: [PATCH v4 07/10] NFSD create new stateid for async copy

Quoted 21 lines · Olga Kornievskaia
Quoted 10 lines · J. Bruce Fields
Quoted 8 lines · Olga Kornievskaia

Generate a new stateid to be used for reply to the asynchronous COPY (this would also be used later by COPY_NOTIFY as well). Associate the stateid with the parent OPEN/LOCK/DELEG stateid that can be freed during the free of the parent stateid. However, right now deciding to bind the lifetime to when the vfs copy is done. This way don't need to keep the nfsd_net structure for the callback. The drawback is that time copy state information is available for query by OFFLOAD_STATUS is slightly less.

Quoted 2 lines

I don't think we're under any obligation to handle OFFLOAD_STATUS after the copy is done, are we?

It depends on how you define “copy is done”. I’d say copy is done once the server sent the CB_OFFLOAD. But I’m choosing to clear the state once the vfs_copy_file_range() is done. Then the callback is queued up (what if there is wait until the callback can be processed) and there is a period where if the client were to send a OFFLOAD_STATUS it should get the bytes representing the full copy but instead the client will get an error back.

Quoted 3 lines

I was worried about what if some implementation depend on querying for the status and stopping to poll only when they received that all bytes were copied and they might not get that with how the server is coded right now?

I don't see the spec providing any guarantee that the copy status be kept around any minimum length of time, so I don't think a client would be correct to do that.

--b.

#24 lore raw

PATCH v4 8/10 NFSD handle OFFLOAD_CANCEL op

In reply to Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +24 -2
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
 	size_t bytes_to_copy;
 	u64 src_pos = copy->cp_src_pos;
 	u64 dst_pos = copy->cp_dst_pos;
+	bool cancelled = false;
 
 	do {
 		bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
 		copy->cp_res.wr_bytes_written += bytes_copied;
 		src_pos += bytes_copied;
 		dst_pos += bytes_copied;
-	} while (bytes_total > 0 && !copy->cp_synchronous);
+		if (!copy->cp_synchronous) {
+			spin_lock(&copy->cps->cp_lock);
+			cancelled = copy->cps->cp_cancelled;
+			spin_unlock(&copy->cps->cp_lock);
+		}
+	} while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
 	return bytes_copied;
 }
 
@@ -1198,6 +1204,10 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 	struct nfsd4_copy *cb_copy;
 
 	copy->nfserr = nfsd4_do_copy(copy, 0);
+
+	if (copy->cps->cp_cancelled)
+		goto out;
+
 	cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
 	if (!cb_copy)
 		goto out;
@@ -1269,7 +1279,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		     struct nfsd4_compound_state *cstate,
 		     union nfsd4_op_u *u)
 {
-	return 0;
+	struct nfsd4_offload_status *os = &u->offload_status;
+	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+	__be32 status;
+	struct nfs4_cp_state *state = NULL;
+
+	status = find_cp_state(nn, &os->stateid, &state);
+	if (state) {
+		spin_lock(&state->cp_lock);
+		state->cp_cancelled = true;
+		spin_unlock(&state->cp_lock);
+	}
+
+	return status;
 }
 
 static __be32
fs/nfsd/nfs4state.c +16 -0
index be59baf..97ab3f8 100644
@@ -752,6 +752,22 @@ static void nfs4_free_deleg(struct nfs4_stid *stid)
 	atomic_long_dec(&num_delegations);
 }
 
+__be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+			    struct nfs4_cp_state **cps)
+{
+	struct nfs4_cp_state *state = NULL;
+
+	if (st->si_opaque.so_clid.cl_id != nn->s2s_cp_cl_id)
+		return nfserr_bad_stateid;
+	spin_lock(&nn->s2s_cp_lock);
+	state = idr_find(&nn->s2s_cp_stateids, st->si_opaque.so_id);
+	spin_unlock(&nn->s2s_cp_lock);
+	if (!state)
+		return nfserr_bad_stateid;
+	*cps = state;
+	return 0;
+}
+
 /*
  * When we recall a delegation, we should be careful not to hand it
  * out again straight away.
fs/nfsd/state.h +4 -0
index 8724955..7a070d5 100644
@@ -111,6 +111,8 @@ struct nfs4_cp_state {
 	stateid_t		cp_stateid;
 	struct list_head	cp_list;	/* per parent nfs4_stid */
 	struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+	bool                    cp_cancelled;   /* copy cancelled */
+	spinlock_t              cp_lock;
 };
 
 /*
@@ -647,6 +649,8 @@ extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
 extern int nfsd4_create_copy_queue(void);
 extern void nfsd4_destroy_copy_queue(void);
+extern __be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+			struct nfs4_cp_state **cps);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
Signature · 1 line
1.8.3.1
#25 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 26 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +1 -0
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
 	size_t bytes_to_copy;
 	u64 src_pos = copy->cp_src_pos;
 	u64 dst_pos = copy->cp_dst_pos;
+	bool cancelled = false;
 
 	do {
 		bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
 		copy->cp_res.wr_bytes_written += bytes_copied;

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 10 lines
fs/nfsd/nfs4proc.c +6 -1
 		src_pos += bytes_copied;
 		dst_pos += bytes_copied;
-	} while (bytes_total > 0 && !copy->cp_synchronous);
+		if (!copy->cp_synchronous) {
+			spin_lock(&copy->cps->cp_lock);
+			cancelled = copy->cps->cp_cancelled;
+			spin_unlock(&copy->cps->cp_lock);
+		}
+	} while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
 	return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

That will help us get rid of the 4MB-at-a-time loop. And will mean we don't need to wait for the next 4MB copy to finish before stopping the loop. Normally I wouldn't expect that to take too long, but it might. And a situation where a cancel is sent is a situation where we're probably more likely to have some problem slowing down the copy.

Also: don't we want OFFLOAD_CANCEL to wait until the cancel has actually taken effect before returning?

I can't see any language in the spec to that affect, but it would seem surprising to me if I got back a succesful response to OFFLOAD_CANCEL and then noticed that the target file was still changing.

--b.

Quoted 86 lines
fs/nfsd/nfs4proc.c +17 -1
 }
 
@@ -1198,6 +1204,10 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 	struct nfsd4_copy *cb_copy;
 
 	copy->nfserr = nfsd4_do_copy(copy, 0);
+
+	if (copy->cps->cp_cancelled)
+		goto out;
+
 	cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
 	if (!cb_copy)
 		goto out;
@@ -1269,7 +1279,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		     struct nfsd4_compound_state *cstate,
 		     union nfsd4_op_u *u)
 {
-	return 0;
+	struct nfsd4_offload_status *os = &u->offload_status;
+	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+	__be32 status;
+	struct nfs4_cp_state *state = NULL;
+
+	status = find_cp_state(nn, &os->stateid, &state);
+	if (state) {
+		spin_lock(&state->cp_lock);
+		state->cp_cancelled = true;
+		spin_unlock(&state->cp_lock);
+	}
+
+	return status;
 }
 
 static __be32
fs/nfsd/nfs4state.c +16 -0
index be59baf..97ab3f8 100644
@@ -752,6 +752,22 @@ static void nfs4_free_deleg(struct nfs4_stid *stid)
 	atomic_long_dec(&num_delegations);
 }
 
+__be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+			    struct nfs4_cp_state **cps)
+{
+	struct nfs4_cp_state *state = NULL;
+
+	if (st->si_opaque.so_clid.cl_id != nn->s2s_cp_cl_id)
+		return nfserr_bad_stateid;
+	spin_lock(&nn->s2s_cp_lock);
+	state = idr_find(&nn->s2s_cp_stateids, st->si_opaque.so_id);
+	spin_unlock(&nn->s2s_cp_lock);
+	if (!state)
+		return nfserr_bad_stateid;
+	*cps = state;
+	return 0;
+}
+
 /*
  * When we recall a delegation, we should be careful not to hand it
  * out again straight away.
fs/nfsd/state.h +4 -0
index 8724955..7a070d5 100644
@@ -111,6 +111,8 @@ struct nfs4_cp_state {
 	stateid_t		cp_stateid;
 	struct list_head	cp_list;	/* per parent nfs4_stid */
 	struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+	bool                    cp_cancelled;   /* copy cancelled */
+	spinlock_t              cp_lock;
 };
 
 /*
@@ -647,6 +649,8 @@ extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
 extern int nfsd4_create_copy_queue(void);
 extern void nfsd4_destroy_copy_queue(void);
+extern __be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+			struct nfs4_cp_state **cps);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
Signature · 1 line
1.8.3.1
#26 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Please advise if you still want the kthread-based implementation or keep the workqueue.

Quoted 105 lines

That will help us get rid of the 4MB-at-a-time loop. And will mean we don't need to wait for the next 4MB copy to finish before stopping the loop. Normally I wouldn't expect that to take too long, but it might. And a situation where a cancel is sent is a situation where we're probably more likely to have some problem slowing down the copy.

Also: don't we want OFFLOAD_CANCEL to wait until the cancel has actually taken effect before returning?

I can't see any language in the spec to that affect, but it would seem surprising to me if I got back a succesful response to OFFLOAD_CANCEL and then noticed that the target file was still changing.

--b.

Quoted 84 lines
fs/nfsd/nfs4proc.c +17 -1
 }
 
@@ -1198,6 +1204,10 @@ static void nfsd4_do_async_copy(struct work_struct *work)
      struct nfsd4_copy *cb_copy;
 
      copy->nfserr = nfsd4_do_copy(copy, 0);
+
+     if (copy->cps->cp_cancelled)
+             goto out;
+
      cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
      if (!cb_copy)
              goto out;
@@ -1269,7 +1279,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
                   struct nfsd4_compound_state *cstate,
                   union nfsd4_op_u *u)
 {
-     return 0;
+     struct nfsd4_offload_status *os = &u->offload_status;
+     struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+     __be32 status;
+     struct nfs4_cp_state *state = NULL;
+
+     status = find_cp_state(nn, &os->stateid, &state);
+     if (state) {
+             spin_lock(&state->cp_lock);
+             state->cp_cancelled = true;
+             spin_unlock(&state->cp_lock);
+     }
+
+     return status;
 }
 
 static __be32
fs/nfsd/nfs4state.c +16 -0
index be59baf..97ab3f8 100644
@@ -752,6 +752,22 @@ static void nfs4_free_deleg(struct nfs4_stid *stid)
      atomic_long_dec(&num_delegations);
 }
 
+__be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+                         struct nfs4_cp_state **cps)
+{
+     struct nfs4_cp_state *state = NULL;
+
+     if (st->si_opaque.so_clid.cl_id != nn->s2s_cp_cl_id)
+             return nfserr_bad_stateid;
+     spin_lock(&nn->s2s_cp_lock);
+     state = idr_find(&nn->s2s_cp_stateids, st->si_opaque.so_id);
+     spin_unlock(&nn->s2s_cp_lock);
+     if (!state)
+             return nfserr_bad_stateid;
+     *cps = state;
+     return 0;
+}
+
 /*
  * When we recall a delegation, we should be careful not to hand it
  * out again straight away.
fs/nfsd/state.h +4 -1
index 8724955..7a070d5 100644
@@ -111,6 +111,8 @@ struct nfs4_cp_state {
      stateid_t               cp_stateid;
      struct list_head        cp_list;        /* per parent nfs4_stid */
      struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+     bool                    cp_cancelled;   /* copy cancelled */
+     spinlock_t              cp_lock;
 };
 
 /*
@@ -647,6 +649,8 @@ extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
 extern int nfsd4_create_copy_queue(void);
 extern void nfsd4_destroy_copy_queue(void);
+extern __be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+                     struct nfs4_cp_state **cps);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#27 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 57 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Quoted 6 lines

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

--b.

Quoted 106 lines
Quoted 106 lines

That will help us get rid of the 4MB-at-a-time loop. And will mean we don't need to wait for the next 4MB copy to finish before stopping the loop. Normally I wouldn't expect that to take too long, but it might. And a situation where a cancel is sent is a situation where we're probably more likely to have some problem slowing down the copy.

Also: don't we want OFFLOAD_CANCEL to wait until the cancel has actually taken effect before returning?

I can't see any language in the spec to that affect, but it would seem surprising to me if I got back a succesful response to OFFLOAD_CANCEL and then noticed that the target file was still changing.

--b.

Quoted 84 lines
fs/nfsd/nfs4proc.c +17 -1
 }
 
@@ -1198,6 +1204,10 @@ static void nfsd4_do_async_copy(struct work_struct *work)
      struct nfsd4_copy *cb_copy;
 
      copy->nfserr = nfsd4_do_copy(copy, 0);
+
+     if (copy->cps->cp_cancelled)
+             goto out;
+
      cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
      if (!cb_copy)
              goto out;
@@ -1269,7 +1279,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
                   struct nfsd4_compound_state *cstate,
                   union nfsd4_op_u *u)
 {
-     return 0;
+     struct nfsd4_offload_status *os = &u->offload_status;
+     struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+     __be32 status;
+     struct nfs4_cp_state *state = NULL;
+
+     status = find_cp_state(nn, &os->stateid, &state);
+     if (state) {
+             spin_lock(&state->cp_lock);
+             state->cp_cancelled = true;
+             spin_unlock(&state->cp_lock);
+     }
+
+     return status;
 }
 
 static __be32
fs/nfsd/nfs4state.c +16 -0
index be59baf..97ab3f8 100644
@@ -752,6 +752,22 @@ static void nfs4_free_deleg(struct nfs4_stid *stid)
      atomic_long_dec(&num_delegations);
 }
 
+__be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+                         struct nfs4_cp_state **cps)
+{
+     struct nfs4_cp_state *state = NULL;
+
+     if (st->si_opaque.so_clid.cl_id != nn->s2s_cp_cl_id)
+             return nfserr_bad_stateid;
+     spin_lock(&nn->s2s_cp_lock);
+     state = idr_find(&nn->s2s_cp_stateids, st->si_opaque.so_id);
+     spin_unlock(&nn->s2s_cp_lock);
+     if (!state)
+             return nfserr_bad_stateid;
+     *cps = state;
+     return 0;
+}
+
 /*
  * When we recall a delegation, we should be careful not to hand it
  * out again straight away.
fs/nfsd/state.h +4 -1
index 8724955..7a070d5 100644
@@ -111,6 +111,8 @@ struct nfs4_cp_state {
      stateid_t               cp_stateid;
      struct list_head        cp_list;        /* per parent nfs4_stid */
      struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+     bool                    cp_cancelled;   /* copy cancelled */
+     spinlock_t              cp_lock;
 };
 
 /*
@@ -647,6 +649,8 @@ extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
 extern int nfsd4_create_copy_queue(void);
 extern void nfsd4_destroy_copy_queue(void);
+extern __be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+                     struct nfs4_cp_state **cps);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#28 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 62 lines · J. Bruce Fields
Quoted 61 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Quoted 8 lines
Quoted 2 lines

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

So I found it. Yes do_splice_direct() will react to somebody sending a ctrl-c and will stop. It calls signal_pendning(). However, in our case, I'm calling kthread_stop() and that sets a different flag and one needs to also check for kthread_should_stop() as a stopping condition. splice.c lacks that.

I hope they can agree that it's a bug. I don't have any luck with VFS...

Quoted 109 lines

--b.

Quoted 106 lines
Quoted 106 lines

That will help us get rid of the 4MB-at-a-time loop. And will mean we don't need to wait for the next 4MB copy to finish before stopping the loop. Normally I wouldn't expect that to take too long, but it might. And a situation where a cancel is sent is a situation where we're probably more likely to have some problem slowing down the copy.

Also: don't we want OFFLOAD_CANCEL to wait until the cancel has actually taken effect before returning?

I can't see any language in the spec to that affect, but it would seem surprising to me if I got back a succesful response to OFFLOAD_CANCEL and then noticed that the target file was still changing.

--b.

Quoted 84 lines
fs/nfsd/nfs4proc.c +17 -1
 }
 
@@ -1198,6 +1204,10 @@ static void nfsd4_do_async_copy(struct work_struct *work)
      struct nfsd4_copy *cb_copy;
 
      copy->nfserr = nfsd4_do_copy(copy, 0);
+
+     if (copy->cps->cp_cancelled)
+             goto out;
+
      cb_copy = kzalloc(sizeof(struct nfsd4_copy), GFP_KERNEL);
      if (!cb_copy)
              goto out;
@@ -1269,7 +1279,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
                   struct nfsd4_compound_state *cstate,
                   union nfsd4_op_u *u)
 {
-     return 0;
+     struct nfsd4_offload_status *os = &u->offload_status;
+     struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+     __be32 status;
+     struct nfs4_cp_state *state = NULL;
+
+     status = find_cp_state(nn, &os->stateid, &state);
+     if (state) {
+             spin_lock(&state->cp_lock);
+             state->cp_cancelled = true;
+             spin_unlock(&state->cp_lock);
+     }
+
+     return status;
 }
 
 static __be32
fs/nfsd/nfs4state.c +16 -0
index be59baf..97ab3f8 100644
@@ -752,6 +752,22 @@ static void nfs4_free_deleg(struct nfs4_stid *stid)
      atomic_long_dec(&num_delegations);
 }
 
+__be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+                         struct nfs4_cp_state **cps)
+{
+     struct nfs4_cp_state *state = NULL;
+
+     if (st->si_opaque.so_clid.cl_id != nn->s2s_cp_cl_id)
+             return nfserr_bad_stateid;
+     spin_lock(&nn->s2s_cp_lock);
+     state = idr_find(&nn->s2s_cp_stateids, st->si_opaque.so_id);
+     spin_unlock(&nn->s2s_cp_lock);
+     if (!state)
+             return nfserr_bad_stateid;
+     *cps = state;
+     return 0;
+}
+
 /*
  * When we recall a delegation, we should be careful not to hand it
  * out again straight away.
fs/nfsd/state.h +4 -1
index 8724955..7a070d5 100644
@@ -111,6 +111,8 @@ struct nfs4_cp_state {
      stateid_t               cp_stateid;
      struct list_head        cp_list;        /* per parent nfs4_stid */
      struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
+     bool                    cp_cancelled;   /* copy cancelled */
+     spinlock_t              cp_lock;
 };
 
 /*
@@ -647,6 +649,8 @@ extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 extern bool nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn);
 extern int nfsd4_create_copy_queue(void);
 extern void nfsd4_destroy_copy_queue(void);
+extern __be32 find_cp_state(struct nfsd_net *nn, stateid_t *st,
+                     struct nfs4_cp_state **cps);
 
 struct nfs4_file *find_file(struct knfsd_fh *fh);
 void put_nfs4_file(struct nfs4_file *fi);
--
Quoted 2 lines

1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#29 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 72 lines · Olga Kornievskaia
Quoted 62 lines · J. Bruce Fields
Quoted 61 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Quoted 8 lines
Quoted 2 lines

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

Quoted 7 lines

So I found it. Yes do_splice_direct() will react to somebody sending a ctrl-c and will stop. It calls signal_pendning(). However, in our case, I'm calling kthread_stop() and that sets a different flag and one needs to also check for kthread_should_stop() as a stopping condition. splice.c lacks that.

I hope they can agree that it's a bug. I don't have any luck with VFS...

Argh. No, it's probably not their bug, I guess kthreads just ignore signals. OK, I can't immediately see what the right thing to do is here....

I do think we need to do something as we want to be able to interrupt and clean up copy threads when we can.

--b.

#30 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 79 lines · J. Bruce Fields
Quoted 78 lines · Olga Kornievskaia
Quoted 62 lines · J. Bruce Fields
Quoted 61 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Quoted 8 lines
Quoted 2 lines

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

So I found it. Yes do_splice_direct() will react to somebody sending a ctrl-c and will stop. It calls signal_pendning(). However, in our case, I'm calling kthread_stop() and that sets a different flag and one needs to also check for kthread_should_stop() as a stopping condition. splice.c lacks that.

Quoted 8 lines
Quoted 1 line

I hope they can agree that it's a bug. I don't have any luck with VFS...

Argh. No, it's probably not their bug, I guess kthreads just ignore signals. OK, I can't immediately see what the right thing to do is here....

I do think we need to do something as we want to be able to interrupt and clean up copy threads when we can.

A bug is not the right word. It would be asking them to accommodate stopping to include kthread_stop condition. Why do you say kthreads ignore signals? You can say that kthread_stop doesn't send a signal.

Also another note, I still can't remove the loop around the call to the vfs_copy_file_range() because it's not guaranteed to copy all the bytes that the call asks for. The implementation of vfs_copy_file_range will do_splice_direct only MAX_RW_COUNT at a time. So the upper layer needs to loop to make sure it copies all the bytes.

If VFS will decide to reject the request to add kthread_should_stop to their conditions, then the loop could be a way to stop every 4MB. Copying 4MB would be the equivalent of what the current synchronous copy does now anyway?

Quoted 6 lines

--b.
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#31 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 86 lines · Olga Kornievskaia
Quoted 85 lines · J. Bruce Fields
Quoted 72 lines · Olga Kornievskaia
Quoted 62 lines · J. Bruce Fields
Quoted 61 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Quoted 8 lines
Quoted 2 lines

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

Quoted 7 lines

So I found it. Yes do_splice_direct() will react to somebody sending a ctrl-c and will stop. It calls signal_pendning(). However, in our case, I'm calling kthread_stop() and that sets a different flag and one needs to also check for kthread_should_stop() as a stopping condition. splice.c lacks that.

I hope they can agree that it's a bug. I don't have any luck with VFS...

Argh. No, it's probably not their bug, I guess kthreads just ignore signals. OK, I can't immediately see what the right thing to do is here....

Quoted 6 lines
Quoted 2 lines

I do think we need to do something as we want to be able to interrupt and clean up copy threads when we can.

A bug is not the right word. It would be asking them to accommodate stopping to include kthread_stop condition. Why do you say kthreads ignore signals? You can say that kthread_stop doesn't send a signal.

I think both are true.

I doubt it's reasonable to add kthread_should_stop everywhere that there are currently checks for signals.

Quoted 5 lines

Also another note, I still can't remove the loop around the call to the vfs_copy_file_range() because it's not guaranteed to copy all the bytes that the call asks for. The implementation of vfs_copy_file_range will do_splice_direct only MAX_RW_COUNT at a time. So the upper layer needs to loop to make sure it copies all the bytes.

MAX_RW_COUNT is about 4 gigs. I'm not sure if it's really a problem to copy only 4 gigs at a time? But, yes, maybe the loop is still worth it.

Quoted 4 lines

If VFS will decide to reject the request to add kthread_should_stop to their conditions, then the loop could be a way to stop every 4MB. Copying 4MB would be the equivalent of what the current synchronous copy does now anyway?

I'm still a little worried about copy threads hanging indefinitely if the peer goes away mid-copy. The ability to signal the copy thread would help.

--b.

#32 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 108 lines · J. Bruce Fields
Quoted 86 lines · Olga Kornievskaia
Quoted 85 lines · J. Bruce Fields
Quoted 72 lines · Olga Kornievskaia
Quoted 62 lines · J. Bruce Fields
Quoted 61 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Quoted 8 lines
Quoted 2 lines

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

Quoted 7 lines

So I found it. Yes do_splice_direct() will react to somebody sending a ctrl-c and will stop. It calls signal_pendning(). However, in our case, I'm calling kthread_stop() and that sets a different flag and one needs to also check for kthread_should_stop() as a stopping condition. splice.c lacks that.

I hope they can agree that it's a bug. I don't have any luck with VFS...

Argh. No, it's probably not their bug, I guess kthreads just ignore signals. OK, I can't immediately see what the right thing to do is here....

Quoted 6 lines
Quoted 2 lines

I do think we need to do something as we want to be able to interrupt and clean up copy threads when we can.

A bug is not the right word. It would be asking them to accommodate stopping to include kthread_stop condition. Why do you say kthreads ignore signals? You can say that kthread_stop doesn't send a signal.

I think both are true.

I doubt it's reasonable to add kthread_should_stop everywhere that there are currently checks for signals.

Quoted 5 lines

Also another note, I still can't remove the loop around the call to the vfs_copy_file_range() because it's not guaranteed to copy all the bytes that the call asks for. The implementation of vfs_copy_file_range will do_splice_direct only MAX_RW_COUNT at a time. So the upper layer needs to loop to make sure it copies all the bytes.

MAX_RW_COUNT is about 4 gigs. I'm not sure if it's really a problem to copy only 4 gigs at a time? But, yes, maybe the loop is still worth it.

Quoted 8 lines
Quoted 4 lines

If VFS will decide to reject the request to add kthread_should_stop to their conditions, then the loop could be a way to stop every 4MB. Copying 4MB would be the equivalent of what the current synchronous copy does now anyway?

I'm still a little worried about copy threads hanging indefinitely if the peer goes away mid-copy. The ability to signal the copy thread would help.

So I don't know if this is ok or not but I can directly set the SIGPENDING bit in the copy task structure from the OFFLOAD_CANCEL thread and that stops the splice copy too.

Quoted 6 lines

--b.
--
To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#33 lore raw

Re: [PATCH v4 08/10] NFSD handle OFFLOAD_CANCEL op

Quoted 114 lines · Olga Kornievskaia
Quoted 113 lines · J. Bruce Fields
Quoted 86 lines · Olga Kornievskaia
Quoted 85 lines · J. Bruce Fields
Quoted 72 lines · Olga Kornievskaia
Quoted 62 lines · J. Bruce Fields
Quoted 61 lines · Olga Kornievskaia
Quoted 32 lines · J. Bruce Fields
Quoted 31 lines · Olga Kornievskaia

Upon receiving OFFLOAD_CANCEL search the list of copy stateids, if found mark it cancelled. If copy has more interations to call vfs_copy_file_range, it'll stop it. Server won't be sending CB_OFFLOAD to the client since it received a cancel.

---
 fs/nfsd/nfs4proc.c  | 26 ++++++++++++++++++++++++--
 fs/nfsd/nfs4state.c | 16 ++++++++++++++++
 fs/nfsd/state.h     |  4 ++++
 3 files changed, 44 insertions(+), 2 deletions(-)
fs/nfsd/nfs4proc.c +3 -1
index 3cddebb..f4f3d93 100644
@@ -1139,6 +1139,7 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
      size_t bytes_to_copy;
      u64 src_pos = copy->cp_src_pos;
      u64 dst_pos = copy->cp_dst_pos;
+     bool cancelled = false;
 
      do {
              bytes_to_copy = min_t(u64, bytes_total, MAX_RW_COUNT);
@@ -1150,7 +1151,12 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
              copy->cp_res.wr_bytes_written += bytes_copied;
              src_pos += bytes_copied;
              dst_pos += bytes_copied;
-     } while (bytes_total > 0 && !copy->cp_synchronous);
+             if (!copy->cp_synchronous) {
+                     spin_lock(&copy->cps->cp_lock);
Quoted 8 lines

fs/nfsd/nfs4proc.c static int _nfsd_copy_file_range(struct nfsd4_copy *copy) · line 1160

Quoted 5 lines
fs/nfsd/nfs4proc.c +4 -0
+                     cancelled = copy->cps->cp_cancelled;
+                     spin_unlock(&copy->cps->cp_lock);
+             }
+     } while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
      return bytes_copied;

I'd rather we sent a signal, and then we won't need this logic--vfs_copy_range() will just return EINTR or something.

Hi Bruce,

Now that I've implemented using the kthread instead of the workqueue, I don't see that it can provide any better guarantee than the work queue. vfs_copy_range() is not interrupted in the middle and returning the EINTR. The function that runs the kthread, it has to at some point call signalled()/kthread_should_stop() function to see if it was signaled and use it to 'stop working instead of continuing on'.

If I were to remove the loop and check (if signaled() || kthread_should_stop()) before and after calling the vfs_copy_file_range(), the copy will either not start if the OFFLOAD_CANCEL was received before copy started or the whole copy would happen.

Even with the loop, I'd be checking after every call for vfs_copy_file_range() just like it was in the current version with the workqueue.

Quoted 8 lines
Quoted 2 lines

Please advise if you still want the kthread-based implementation or keep the workqueue.

That's interesting.

To me that sounds like a bug somewhere under vfs_copy_file_range(). splice_direct_to_actor() can do long-running copies, so it should be interruptible, shouldn't it?

Quoted 7 lines

So I found it. Yes do_splice_direct() will react to somebody sending a ctrl-c and will stop. It calls signal_pendning(). However, in our case, I'm calling kthread_stop() and that sets a different flag and one needs to also check for kthread_should_stop() as a stopping condition. splice.c lacks that.

I hope they can agree that it's a bug. I don't have any luck with VFS...

Argh. No, it's probably not their bug, I guess kthreads just ignore signals. OK, I can't immediately see what the right thing to do is here....

Quoted 6 lines
Quoted 2 lines

I do think we need to do something as we want to be able to interrupt and clean up copy threads when we can.

A bug is not the right word. It would be asking them to accommodate stopping to include kthread_stop condition. Why do you say kthreads ignore signals? You can say that kthread_stop doesn't send a signal.

I think both are true.

I doubt it's reasonable to add kthread_should_stop everywhere that there are currently checks for signals.

Quoted 5 lines

Also another note, I still can't remove the loop around the call to the vfs_copy_file_range() because it's not guaranteed to copy all the bytes that the call asks for. The implementation of vfs_copy_file_range will do_splice_direct only MAX_RW_COUNT at a time. So the upper layer needs to loop to make sure it copies all the bytes.

MAX_RW_COUNT is about 4 gigs. I'm not sure if it's really a problem to copy only 4 gigs at a time? But, yes, maybe the loop is still worth it.

Quoted 4 lines

If VFS will decide to reject the request to add kthread_should_stop to their conditions, then the loop could be a way to stop every 4MB. Copying 4MB would be the equivalent of what the current synchronous copy does now anyway?

Quoted 7 lines
Quoted 3 lines

I'm still a little worried about copy threads hanging indefinitely if the peer goes away mid-copy. The ability to signal the copy thread would help.

So I don't know if this is ok or not but I can directly set the SIGPENDING bit in the copy task structure from the OFFLOAD_CANCEL thread and that stops the splice copy too.

Sorry I'm a bit late here...

The main reason (IMO) to use your own thread here is to avoid squatting on top of workqueue threads for too long. Workqueue threads are shared by all workqueues in the system, so when you use one to run your long- running in-kernel copy task, you're potentially depriving other (quite possibly more immediate) work from getting done. Spawning a thread in the context of copy offload seems like negligible overhead and is "nicer" to the rest of the system.

There's also the problem of how to interrupt a long-running copy. Early on, kthreadd will ignore_signals(), so kthreads (including workqueue threads) by default will always ignore them because they inherit that.

If you decide to use workqueues, you _must_ reinstate the signal handler state for the thread when you're done. Otherwise you'll leave other work receptive to signals (which may be problematic). There's also the problem of figuring out which kthread to send a signal when you want to interrupt it. Not trivial once you've handed it off to the workqueue.

So, I think a kthread is also just simpler here. When you spawn a thread, you need to make it be receptive to signals to allow vfs_copy_file_range to see them and stop what it's doing. It's your choice whether to use that signal as a key to shut down (like nfsd does) or whether you want to just use kthread_stop. nfsd() does this, so you may be able to use it to guide you.

The simplest approach (IMO) is to just check signal_pending when the copy returns, and cleanup/exit the thread when it returns true. You need to be able to deal with signals anyway to interrupt a copy, so using the kthread_stop mechanism doesn't really add anything there.

Hope this helps!

Signature · 1 line
Jeff Layton <[email protected]>
#34 lore raw

PATCH v4 9/10 NFSD support OFFLOAD_STATUS

In reply to Olga Kornievskaia

Update number of bytes copied in the copy state and query that value under lock if OFFLOAD_STATUS operation received.

---
 fs/nfsd/nfs4proc.c | 16 +++++++++++++++-
 fs/nfsd/state.h    |  1 +
 2 files changed, 16 insertions(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +15 -1
index f4f3d93..74bfa4b 100644
@@ -1154,6 +1154,8 @@ static int _nfsd_copy_file_range(struct nfsd4_copy *copy)
 		if (!copy->cp_synchronous) {
 			spin_lock(&copy->cps->cp_lock);
 			cancelled = copy->cps->cp_cancelled;
+			copy->cps->cp_bytes_copied =
+				copy->cp_res.wr_bytes_written;
 			spin_unlock(&copy->cps->cp_lock);
 		}
 	} while (bytes_total > 0 && !copy->cp_synchronous && !cancelled);
@@ -1321,7 +1323,19 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		     struct nfsd4_compound_state *cstate,
 		     union nfsd4_op_u *u)
 {
-	return nfserr_notsupp;
+	struct nfsd4_offload_status *os = &u->offload_status;
+	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+	__be32 status;
+	struct nfs4_cp_state *state = NULL;
+
+	status = find_cp_state(nn, &os->stateid, &state);
+
+	if (state) {
+		spin_lock(&state->cp_lock);
+		os->count = state->cp_bytes_copied;
+		spin_unlock(&state->cp_lock);
+	}
+	return status;
 }
 
 static __be32
fs/nfsd/state.h +1 -0
index 7a070d5..c31025c 100644
@@ -113,6 +113,7 @@ struct nfs4_cp_state {
 	struct nfs4_stid        *cp_p_stid;     /* pointer to parent */
 	bool                    cp_cancelled;   /* copy cancelled */
 	spinlock_t              cp_lock;
+	ssize_t                 cp_bytes_copied;/* copy progress */
 };
 
 /*
Signature · 1 line
1.8.3.1
#35 lore raw

PATCH v4 10/10 NFSD stop queued async copies on client shutdown

In reply to Olga Kornievskaia

If client is shutting down and there are still async copies going on, then stop queued async copies.

---
 fs/nfsd/nfs4proc.c  | 9 +++++++++
 fs/nfsd/nfs4state.c | 1 +
 fs/nfsd/state.h     | 2 ++
 3 files changed, 12 insertions(+)
fs/nfsd/nfs4proc.c +9 -0
index 74bfa4b..8d0c87f 100644
@@ -91,6 +91,12 @@ void nfsd4_destroy_copy_queue(void)
 	destroy_workqueue(copy_wq);
 }
 
+void nfsd4_shutdown_copy(struct nfs4_client *clp)
+{
+	set_bit(NFSD4_CLIENT_COPY_KILL, &clp->cl_flags);
+	flush_workqueue(copy_wq);
+}
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1205,6 +1211,9 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		container_of(work, struct nfsd4_copy, cp_work);
 	struct nfsd4_copy *cb_copy;
 
+	if (test_bit(NFSD4_CLIENT_COPY_KILL, &copy->cp_clp->cl_flags))
+		goto out;
+
 	copy->nfserr = nfsd4_do_copy(copy, 0);
 
 	if (copy->cps->cp_cancelled)
fs/nfsd/nfs4state.c +1 -0
index 97ab3f8..f4b193e 100644
@@ -1961,6 +1961,7 @@ static __be32 mark_client_expired_locked(struct nfs4_client *clp)
 	}
 	nfsd4_return_all_client_layouts(clp);
 	nfsd4_shutdown_callback(clp);
+	nfsd4_shutdown_copy(clp);
 	if (clp->cl_cb_conn.cb_xprt)
 		svc_xprt_put(clp->cl_cb_conn.cb_xprt);
 	free_client(clp);
fs/nfsd/state.h +2 -0
index c31025c..55f09a9 100644
@@ -336,6 +336,7 @@ struct nfs4_client {
 #define NFSD4_CLIENT_RECLAIM_COMPLETE	(3)	/* reclaim_complete done */
 #define NFSD4_CLIENT_CONFIRMED		(4)	/* client is confirmed */
 #define NFSD4_CLIENT_UPCALL_LOCK	(5)	/* upcall serialization */
+#define NFSD4_CLIENT_COPY_KILL		(6)	/* stop copy workqueue */
 #define NFSD4_CLIENT_CB_FLAG_MASK	(1 << NFSD4_CLIENT_CB_UPDATE | \
 					 1 << NFSD4_CLIENT_CB_KILL)
 	unsigned long		cl_flags;
@@ -644,6 +645,7 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern int nfsd4_create_callback_queue(void);
 extern void nfsd4_destroy_callback_queue(void);
 extern void nfsd4_shutdown_callback(struct nfs4_client *);
+extern void nfsd4_shutdown_copy(struct nfs4_client *clp);
 extern void nfsd4_prepare_cb_recall(struct nfs4_delegation *dp);
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 							struct nfsd_net *nn);
Signature · 1 line
1.8.3.1
#36 lore raw

Re: [PATCH v4 10/10] NFSD stop queued async copies on client shutdown

Quoted 15 lines · Olga Kornievskaia

If client is shutting down and there are still async copies going on, then stop queued async copies.

---
 fs/nfsd/nfs4proc.c  | 9 +++++++++
 fs/nfsd/nfs4state.c | 1 +
 fs/nfsd/state.h     | 2 ++
 3 files changed, 12 insertions(+)
fs/nfsd/nfs4proc.c +0 -0
index 74bfa4b..8d0c87f 100644

fs/nfsd/nfs4proc.c void nfsd4_destroy_copy_queue(void) · line 97

Quoted 7 lines
fs/nfsd/nfs4proc.c +4 -0
@@ -91,6 +91,12 @@ void nfsd4_destroy_copy_queue(void)
 	destroy_workqueue(copy_wq);
 }
 
+void nfsd4_shutdown_copy(struct nfs4_client *clp)
+{
+	set_bit(NFSD4_CLIENT_COPY_KILL, &clp->cl_flags);
+	flush_workqueue(copy_wq);

Looks like that will just stop new copies and wait on any ongoing ones. That could be a long wait.

A signal should help there too.

If you still need a "this client is dying" bit in cl_flags, you could probably just use CB_KILL instead of this new one.

--b.

Quoted 50 lines
fs/nfsd/nfs4proc.c +5 -0
+}
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
 static u32 nfsd_attrmask[] = {
@@ -1205,6 +1211,9 @@ static void nfsd4_do_async_copy(struct work_struct *work)
 		container_of(work, struct nfsd4_copy, cp_work);
 	struct nfsd4_copy *cb_copy;
 
+	if (test_bit(NFSD4_CLIENT_COPY_KILL, &copy->cp_clp->cl_flags))
+		goto out;
+
 	copy->nfserr = nfsd4_do_copy(copy, 0);
 
 	if (copy->cps->cp_cancelled)
fs/nfsd/nfs4state.c +1 -0
index 97ab3f8..f4b193e 100644
@@ -1961,6 +1961,7 @@ static __be32 mark_client_expired_locked(struct nfs4_client *clp)
 	}
 	nfsd4_return_all_client_layouts(clp);
 	nfsd4_shutdown_callback(clp);
+	nfsd4_shutdown_copy(clp);
 	if (clp->cl_cb_conn.cb_xprt)
 		svc_xprt_put(clp->cl_cb_conn.cb_xprt);
 	free_client(clp);
fs/nfsd/state.h +2 -0
index c31025c..55f09a9 100644
@@ -336,6 +336,7 @@ struct nfs4_client {
 #define NFSD4_CLIENT_RECLAIM_COMPLETE	(3)	/* reclaim_complete done */
 #define NFSD4_CLIENT_CONFIRMED		(4)	/* client is confirmed */
 #define NFSD4_CLIENT_UPCALL_LOCK	(5)	/* upcall serialization */
+#define NFSD4_CLIENT_COPY_KILL		(6)	/* stop copy workqueue */
 #define NFSD4_CLIENT_CB_FLAG_MASK	(1 << NFSD4_CLIENT_CB_UPDATE | \
 					 1 << NFSD4_CLIENT_CB_KILL)
 	unsigned long		cl_flags;
@@ -644,6 +645,7 @@ extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
 extern int nfsd4_create_callback_queue(void);
 extern void nfsd4_destroy_callback_queue(void);
 extern void nfsd4_shutdown_callback(struct nfs4_client *);
+extern void nfsd4_shutdown_copy(struct nfs4_client *clp);
 extern void nfsd4_prepare_cb_recall(struct nfs4_delegation *dp);
 extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(const char *name,
 							struct nfsd_net *nn);
Signature · 1 line
1.8.3.1