nfsd_setattr() can kick off a CB_RECALL (via notify_change() -> break_lease()) if a delegation is present. Before returning NFS4ERR_DELAY, give the client holding that delegation a chance to return it and then retry the nfsd_setattr() again, once. Link: https://bugzilla.linux-nfs.org/show_bug.cgi?id=354 Tested-by: Igor Mammedov <imammedo@xxxxxxxxxx> Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx> --- fs/nfsd/nfs4proc.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 14f07d3d6c48..5b18a71f1043 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1142,7 +1142,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, { struct nfsd4_setattr *setattr = &u->setattr; __be32 status = nfs_ok; - int err; + int err, retries; if (setattr->sa_iattr.ia_valid & ATTR_SIZE) { status = nfs4_preprocess_stateid_op(rqstp, cstate, @@ -1173,8 +1173,21 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, &setattr->sa_label); if (status) goto out; - status = nfsd_setattr(rqstp, &cstate->current_fh, &setattr->sa_iattr, - 0, (time64_t)0); + + retries = 1; + do { + status = nfsd_setattr(rqstp, &cstate->current_fh, + &setattr->sa_iattr, 0, (time64_t)0); + if (status != nfserr_jukebox) + break; + if (!retries--) + break; + if (!nfsd4_wait_for_delegreturn(rqstp, &cstate->current_fh)) + break; + + fh_clear_pre_post_attrs(&cstate->current_fh); + } while (1); + out: fh_drop_write(&cstate->current_fh); return status;