Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

NFS: Use NFSv4.2's OFFLOAD_STATUS operation

We've found that there are cases where a transport disconnection
results in the loss of callback RPCs. NFS servers typically do not
retransmit callback operations after a disconnect.

This can be a problem for the Linux NFS client's current
implementation of asynchronous COPY, which waits indefinitely for a
CB_OFFLOAD callback. If a transport disconnect occurs while an async
COPY is running, there's a good chance the client will never get the
completing CB_OFFLOAD.

Fix this by implementing the OFFLOAD_STATUS operation so that the
Linux NFS client can probe the NFS server if it doesn't see a
CB_OFFLOAD in a reasonable amount of time.

This patch implements a simplistic check. As future work, the client
might also be able to detect whether there is no forward progress on
the request asynchronous COPY operation, and CANCEL it.

Suggested-by: Olga Kornievskaia <kolga@netapp.com>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=218735
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Reviewed-by: Benjamin Coddington <bcodding@redhat.com>
Link: https://lore.kernel.org/r/20250113153235.48706-15-cel@kernel.org
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>

authored by

Chuck Lever and committed by
Trond Myklebust
adcc0aef 77dd8a30

+59 -11
+59 -11
fs/nfs/nfs42proc.c
··· 175 175 return err; 176 176 } 177 177 178 + static void nfs4_copy_dequeue_callback(struct nfs_server *dst_server, 179 + struct nfs_server *src_server, 180 + struct nfs4_copy_state *copy) 181 + { 182 + spin_lock(&dst_server->nfs_client->cl_lock); 183 + list_del_init(&copy->copies); 184 + spin_unlock(&dst_server->nfs_client->cl_lock); 185 + if (dst_server != src_server) { 186 + spin_lock(&src_server->nfs_client->cl_lock); 187 + list_del_init(&copy->src_copies); 188 + spin_unlock(&src_server->nfs_client->cl_lock); 189 + } 190 + } 191 + 178 192 static int handle_async_copy(struct nfs42_copy_res *res, 179 193 struct nfs_server *dst_server, 180 194 struct nfs_server *src_server, ··· 198 184 bool *restart) 199 185 { 200 186 struct nfs4_copy_state *copy, *tmp_copy = NULL, *iter; 201 - int status = NFS4_OK; 202 187 struct nfs_open_context *dst_ctx = nfs_file_open_context(dst); 203 188 struct nfs_open_context *src_ctx = nfs_file_open_context(src); 189 + struct nfs_client *clp = dst_server->nfs_client; 190 + unsigned long timeout = 3 * HZ; 191 + int status = NFS4_OK; 192 + u64 copied; 204 193 205 194 copy = kzalloc(sizeof(struct nfs4_copy_state), GFP_KERNEL); 206 195 if (!copy) ··· 241 224 spin_unlock(&src_server->nfs_client->cl_lock); 242 225 } 243 226 244 - status = wait_for_completion_interruptible(&copy->completion); 245 - spin_lock(&dst_server->nfs_client->cl_lock); 246 - list_del_init(&copy->copies); 247 - spin_unlock(&dst_server->nfs_client->cl_lock); 248 - if (dst_server != src_server) { 249 - spin_lock(&src_server->nfs_client->cl_lock); 250 - list_del_init(&copy->src_copies); 251 - spin_unlock(&src_server->nfs_client->cl_lock); 252 - } 227 + wait: 228 + status = wait_for_completion_interruptible_timeout(&copy->completion, 229 + timeout); 230 + if (!status) 231 + goto timeout; 232 + nfs4_copy_dequeue_callback(dst_server, src_server, copy); 253 233 if (status == -ERESTARTSYS) { 254 234 goto out_cancel; 255 235 } else if (copy->flags || copy->error == NFS4ERR_PARTNER_NO_AUTH) { ··· 256 242 } 257 243 out: 258 244 res->write_res.count = copy->count; 245 + /* Copy out the updated write verifier provided by CB_OFFLOAD. */ 259 246 memcpy(&res->write_res.verifier, &copy->verf, sizeof(copy->verf)); 260 247 status = -copy->error; 261 248 ··· 267 252 nfs42_do_offload_cancel_async(dst, &copy->stateid); 268 253 if (!nfs42_files_from_same_server(src, dst)) 269 254 nfs42_do_offload_cancel_async(src, src_stateid); 255 + goto out_free; 256 + timeout: 257 + timeout <<= 1; 258 + if (timeout > (clp->cl_lease_time >> 1)) 259 + timeout = clp->cl_lease_time >> 1; 260 + status = nfs42_proc_offload_status(dst, &copy->stateid, &copied); 261 + if (status == -EINPROGRESS) 262 + goto wait; 263 + nfs4_copy_dequeue_callback(dst_server, src_server, copy); 264 + switch (status) { 265 + case 0: 266 + /* The server recognized the copy stateid, so it hasn't 267 + * rebooted. Don't overwrite the verifier returned in the 268 + * COPY result. */ 269 + res->write_res.count = copied; 270 + goto out_free; 271 + case -EREMOTEIO: 272 + /* COPY operation failed on the server. */ 273 + status = -EOPNOTSUPP; 274 + res->write_res.count = copied; 275 + goto out_free; 276 + case -EBADF: 277 + /* Server did not recognize the copy stateid. It has 278 + * probably restarted and lost the plot. */ 279 + res->write_res.count = 0; 280 + status = -EOPNOTSUPP; 281 + break; 282 + case -EOPNOTSUPP: 283 + /* RFC 7862 REQUIREs server to support OFFLOAD_STATUS when 284 + * it has signed up for an async COPY, so server is not 285 + * spec-compliant. */ 286 + res->write_res.count = 0; 287 + } 270 288 goto out_free; 271 289 } 272 290 ··· 691 643 * Other negative errnos indicate the client could not complete the 692 644 * request. 693 645 */ 694 - static int __maybe_unused 646 + static int 695 647 nfs42_proc_offload_status(struct file *dst, nfs4_stateid *stateid, u64 *copied) 696 648 { 697 649 struct inode *inode = file_inode(dst);