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

NFS: fix delayed delegation return handling

Rework this code that was totally busted at least as of my most
recent changes. Introduce a separate list for delayed delegations
so that they can't get lost and don't clutter up the returns list.
Add a missing spin_unlock in the helper marking it as a regular
pending return.

Fixes: 0ebe655bd033 ("NFS: add a separate delegation return list")
Reported-by: Chris Mason <clm@meta.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>

authored by

Christoph Hellwig and committed by
Anna Schumaker
4039fbed 94b88865

+15 -22
+1
fs/nfs/client.c
··· 1063 1063 spin_lock_init(&server->delegations_lock); 1064 1064 INIT_LIST_HEAD(&server->delegations_return); 1065 1065 INIT_LIST_HEAD(&server->delegations_lru); 1066 + INIT_LIST_HEAD(&server->delegations_delayed); 1066 1067 INIT_LIST_HEAD(&server->layouts); 1067 1068 INIT_LIST_HEAD(&server->state_owners_lru); 1068 1069 INIT_LIST_HEAD(&server->ss_copies);
+12 -18
fs/nfs/delegation.c
··· 336 336 337 337 spin_lock(&delegation->lock); 338 338 if (delegation->inode && 339 - !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) { 340 - clear_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags); 339 + !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) 341 340 return_now = true; 342 - } 343 341 spin_unlock(&delegation->lock); 344 342 345 343 if (!return_now) { ··· 584 586 out_return: 585 587 return nfs_do_return_delegation(inode, delegation, issync); 586 588 delay: 587 - set_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags); 588 - set_bit(NFS4SERV_DELEGRETURN_DELAYED, &server->delegation_flags); 589 + spin_lock(&server->delegations_lock); 590 + if (list_empty(&delegation->entry)) 591 + refcount_inc(&delegation->refcount); 592 + list_move_tail(&delegation->entry, &server->delegations_return); 593 + spin_unlock(&server->delegations_lock); 589 594 set_bit(NFS4CLNT_DELEGRETURN_DELAYED, &server->nfs_client->cl_state); 590 595 abort: 591 596 clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags); ··· 617 616 spin_unlock(&delegation->lock); 618 617 goto out_put_delegation; 619 618 } 620 - if (test_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags) || 621 - test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) || 619 + if (test_bit(NFS_DELEGATION_REVOKED, &delegation->flags) || 622 620 test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) { 623 621 spin_unlock(&delegation->lock); 624 622 goto out_put_inode; 625 623 } 626 - clear_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags); 627 624 spin_unlock(&delegation->lock); 628 625 629 626 nfs_clear_verifier_delegated(inode); 630 627 631 628 err = nfs_end_delegation_return(inode, delegation, false); 632 - if (err) { 633 - nfs_mark_return_delegation(server, delegation); 634 - goto out_put_inode; 635 - } 636 629 637 630 out_put_inode: 638 631 iput(inode); ··· 703 708 704 709 static bool nfs_server_clear_delayed_delegations(struct nfs_server *server) 705 710 { 706 - struct nfs_delegation *d; 707 711 bool ret = false; 708 712 709 - if (!test_and_clear_bit(NFS4SERV_DELEGRETURN_DELAYED, 710 - &server->delegation_flags)) 713 + if (list_empty_careful(&server->delegations_delayed)) 711 714 return false; 712 715 713 716 spin_lock(&server->delegations_lock); 714 - list_for_each_entry_rcu(d, &server->delegations_return, entry) { 715 - if (test_bit(NFS_DELEGATION_RETURN_DELAYED, &d->flags)) 716 - clear_bit(NFS_DELEGATION_RETURN_DELAYED, &d->flags); 717 + if (!list_empty(&server->delegations_delayed)) { 718 + list_splice_tail_init(&server->delegations_delayed, 719 + &server->delegations_return); 717 720 ret = true; 718 721 } 722 + spin_unlock(&server->delegations_lock); 719 723 720 724 return ret; 721 725 }
-1
fs/nfs/delegation.h
··· 37 37 NFS_DELEGATION_RETURNING, 38 38 NFS_DELEGATION_REVOKED, 39 39 NFS_DELEGATION_TEST_EXPIRED, 40 - NFS_DELEGATION_RETURN_DELAYED, 41 40 NFS_DELEGATION_DELEGTIME, 42 41 }; 43 42
+1 -2
fs/nfs/nfs4trace.h
··· 991 991 { BIT(NFS_DELEGATION_REFERENCED), "REFERENCED" }, \ 992 992 { BIT(NFS_DELEGATION_RETURNING), "RETURNING" }, \ 993 993 { BIT(NFS_DELEGATION_REVOKED), "REVOKED" }, \ 994 - { BIT(NFS_DELEGATION_TEST_EXPIRED), "TEST_EXPIRED" }, \ 995 - { BIT(NFS_DELEGATION_RETURN_DELAYED), "RETURN_DELAYED" }) 994 + { BIT(NFS_DELEGATION_TEST_EXPIRED), "TEST_EXPIRED" }) 996 995 997 996 DECLARE_EVENT_CLASS(nfs4_delegation_event, 998 997 TP_PROTO(
+1 -1
include/linux/nfs_fs_sb.h
··· 260 260 spinlock_t delegations_lock; 261 261 struct list_head delegations_return; 262 262 struct list_head delegations_lru; 263 + struct list_head delegations_delayed; 263 264 atomic_long_t nr_active_delegations; 264 265 unsigned int delegation_hash_mask; 265 266 struct hlist_head *delegation_hash_table; ··· 269 268 270 269 unsigned long delegation_flags; 271 270 #define NFS4SERV_DELEGATION_EXPIRED (1) 272 - #define NFS4SERV_DELEGRETURN_DELAYED (2) 273 271 unsigned long delegation_gen; 274 272 unsigned long mig_gen; 275 273 unsigned long mig_status;