diff options
author | Jeff Layton <jlayton@redhat.com> | 2016-10-20 09:34:31 -0400 |
---|---|---|
committer | J. Bruce Fields <bfields@redhat.com> | 2016-10-24 16:51:21 -0400 |
commit | 0cc11a61b80a1ab1d12f1597b27b8b45ef8bac4a (patch) | |
tree | 391b3b25eb0ac4780c5498f76fef1927f59fe907 /fs | |
parent | 07d9a380680d1c0eb51ef87ff2eab5c994949e69 (diff) |
nfsd: move blocked lock handling under a dedicated spinlock
Bruce was hitting some lockdep warnings in testing, showing that we
could hit a deadlock with the new CB_NOTIFY_LOCK handling, involving a
rather complex situation involving four different spinlocks.
The crux of the matter is that we end up taking the nn->client_lock in
the lm_notify handler. The simplest fix is to just declare a new
per-nfsd_net spinlock to protect the new CB_NOTIFY_LOCK structures.
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/nfsd/netns.h | 5 | ||||
-rw-r--r-- | fs/nfsd/nfs4state.c | 28 |
2 files changed, 20 insertions, 13 deletions
diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index b10d557f9c9e..ee36efd5aece 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h | |||
@@ -84,6 +84,8 @@ struct nfsd_net { | |||
84 | struct list_head client_lru; | 84 | struct list_head client_lru; |
85 | struct list_head close_lru; | 85 | struct list_head close_lru; |
86 | struct list_head del_recall_lru; | 86 | struct list_head del_recall_lru; |
87 | |||
88 | /* protected by blocked_locks_lock */ | ||
87 | struct list_head blocked_locks_lru; | 89 | struct list_head blocked_locks_lru; |
88 | 90 | ||
89 | struct delayed_work laundromat_work; | 91 | struct delayed_work laundromat_work; |
@@ -91,6 +93,9 @@ struct nfsd_net { | |||
91 | /* client_lock protects the client lru list and session hash table */ | 93 | /* client_lock protects the client lru list and session hash table */ |
92 | spinlock_t client_lock; | 94 | spinlock_t client_lock; |
93 | 95 | ||
96 | /* protects blocked_locks_lru */ | ||
97 | spinlock_t blocked_locks_lock; | ||
98 | |||
94 | struct file *rec_file; | 99 | struct file *rec_file; |
95 | bool in_grace; | 100 | bool in_grace; |
96 | const struct nfsd4_client_tracking_ops *client_tracking_ops; | 101 | const struct nfsd4_client_tracking_ops *client_tracking_ops; |
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 9752beb78659..95474196a17e 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c | |||
@@ -217,7 +217,7 @@ find_blocked_lock(struct nfs4_lockowner *lo, struct knfsd_fh *fh, | |||
217 | { | 217 | { |
218 | struct nfsd4_blocked_lock *cur, *found = NULL; | 218 | struct nfsd4_blocked_lock *cur, *found = NULL; |
219 | 219 | ||
220 | spin_lock(&nn->client_lock); | 220 | spin_lock(&nn->blocked_locks_lock); |
221 | list_for_each_entry(cur, &lo->lo_blocked, nbl_list) { | 221 | list_for_each_entry(cur, &lo->lo_blocked, nbl_list) { |
222 | if (fh_match(fh, &cur->nbl_fh)) { | 222 | if (fh_match(fh, &cur->nbl_fh)) { |
223 | list_del_init(&cur->nbl_list); | 223 | list_del_init(&cur->nbl_list); |
@@ -226,7 +226,7 @@ find_blocked_lock(struct nfs4_lockowner *lo, struct knfsd_fh *fh, | |||
226 | break; | 226 | break; |
227 | } | 227 | } |
228 | } | 228 | } |
229 | spin_unlock(&nn->client_lock); | 229 | spin_unlock(&nn->blocked_locks_lock); |
230 | if (found) | 230 | if (found) |
231 | posix_unblock_lock(&found->nbl_lock); | 231 | posix_unblock_lock(&found->nbl_lock); |
232 | return found; | 232 | return found; |
@@ -4665,7 +4665,7 @@ nfs4_laundromat(struct nfsd_net *nn) | |||
4665 | * indefinitely once the lock does become free. | 4665 | * indefinitely once the lock does become free. |
4666 | */ | 4666 | */ |
4667 | BUG_ON(!list_empty(&reaplist)); | 4667 | BUG_ON(!list_empty(&reaplist)); |
4668 | spin_lock(&nn->client_lock); | 4668 | spin_lock(&nn->blocked_locks_lock); |
4669 | while (!list_empty(&nn->blocked_locks_lru)) { | 4669 | while (!list_empty(&nn->blocked_locks_lru)) { |
4670 | nbl = list_first_entry(&nn->blocked_locks_lru, | 4670 | nbl = list_first_entry(&nn->blocked_locks_lru, |
4671 | struct nfsd4_blocked_lock, nbl_lru); | 4671 | struct nfsd4_blocked_lock, nbl_lru); |
@@ -4678,7 +4678,7 @@ nfs4_laundromat(struct nfsd_net *nn) | |||
4678 | list_move(&nbl->nbl_lru, &reaplist); | 4678 | list_move(&nbl->nbl_lru, &reaplist); |
4679 | list_del_init(&nbl->nbl_list); | 4679 | list_del_init(&nbl->nbl_list); |
4680 | } | 4680 | } |
4681 | spin_unlock(&nn->client_lock); | 4681 | spin_unlock(&nn->blocked_locks_lock); |
4682 | 4682 | ||
4683 | while (!list_empty(&reaplist)) { | 4683 | while (!list_empty(&reaplist)) { |
4684 | nbl = list_first_entry(&nn->blocked_locks_lru, | 4684 | nbl = list_first_entry(&nn->blocked_locks_lru, |
@@ -5439,13 +5439,13 @@ nfsd4_lm_notify(struct file_lock *fl) | |||
5439 | bool queue = false; | 5439 | bool queue = false; |
5440 | 5440 | ||
5441 | /* An empty list means that something else is going to be using it */ | 5441 | /* An empty list means that something else is going to be using it */ |
5442 | spin_lock(&nn->client_lock); | 5442 | spin_lock(&nn->blocked_locks_lock); |
5443 | if (!list_empty(&nbl->nbl_list)) { | 5443 | if (!list_empty(&nbl->nbl_list)) { |
5444 | list_del_init(&nbl->nbl_list); | 5444 | list_del_init(&nbl->nbl_list); |
5445 | list_del_init(&nbl->nbl_lru); | 5445 | list_del_init(&nbl->nbl_lru); |
5446 | queue = true; | 5446 | queue = true; |
5447 | } | 5447 | } |
5448 | spin_unlock(&nn->client_lock); | 5448 | spin_unlock(&nn->blocked_locks_lock); |
5449 | 5449 | ||
5450 | if (queue) | 5450 | if (queue) |
5451 | nfsd4_run_cb(&nbl->nbl_cb); | 5451 | nfsd4_run_cb(&nbl->nbl_cb); |
@@ -5868,10 +5868,10 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, | |||
5868 | 5868 | ||
5869 | if (fl_flags & FL_SLEEP) { | 5869 | if (fl_flags & FL_SLEEP) { |
5870 | nbl->nbl_time = jiffies; | 5870 | nbl->nbl_time = jiffies; |
5871 | spin_lock(&nn->client_lock); | 5871 | spin_lock(&nn->blocked_locks_lock); |
5872 | list_add_tail(&nbl->nbl_list, &lock_sop->lo_blocked); | 5872 | list_add_tail(&nbl->nbl_list, &lock_sop->lo_blocked); |
5873 | list_add_tail(&nbl->nbl_lru, &nn->blocked_locks_lru); | 5873 | list_add_tail(&nbl->nbl_lru, &nn->blocked_locks_lru); |
5874 | spin_unlock(&nn->client_lock); | 5874 | spin_unlock(&nn->blocked_locks_lock); |
5875 | } | 5875 | } |
5876 | 5876 | ||
5877 | err = vfs_lock_file(filp, F_SETLK, file_lock, conflock); | 5877 | err = vfs_lock_file(filp, F_SETLK, file_lock, conflock); |
@@ -5900,10 +5900,10 @@ out: | |||
5900 | if (nbl) { | 5900 | if (nbl) { |
5901 | /* dequeue it if we queued it before */ | 5901 | /* dequeue it if we queued it before */ |
5902 | if (fl_flags & FL_SLEEP) { | 5902 | if (fl_flags & FL_SLEEP) { |
5903 | spin_lock(&nn->client_lock); | 5903 | spin_lock(&nn->blocked_locks_lock); |
5904 | list_del_init(&nbl->nbl_list); | 5904 | list_del_init(&nbl->nbl_list); |
5905 | list_del_init(&nbl->nbl_lru); | 5905 | list_del_init(&nbl->nbl_lru); |
5906 | spin_unlock(&nn->client_lock); | 5906 | spin_unlock(&nn->blocked_locks_lock); |
5907 | } | 5907 | } |
5908 | free_blocked_lock(nbl); | 5908 | free_blocked_lock(nbl); |
5909 | } | 5909 | } |
@@ -6943,9 +6943,11 @@ static int nfs4_state_create_net(struct net *net) | |||
6943 | INIT_LIST_HEAD(&nn->client_lru); | 6943 | INIT_LIST_HEAD(&nn->client_lru); |
6944 | INIT_LIST_HEAD(&nn->close_lru); | 6944 | INIT_LIST_HEAD(&nn->close_lru); |
6945 | INIT_LIST_HEAD(&nn->del_recall_lru); | 6945 | INIT_LIST_HEAD(&nn->del_recall_lru); |
6946 | INIT_LIST_HEAD(&nn->blocked_locks_lru); | ||
6947 | spin_lock_init(&nn->client_lock); | 6946 | spin_lock_init(&nn->client_lock); |
6948 | 6947 | ||
6948 | spin_lock_init(&nn->blocked_locks_lock); | ||
6949 | INIT_LIST_HEAD(&nn->blocked_locks_lru); | ||
6950 | |||
6949 | INIT_DELAYED_WORK(&nn->laundromat_work, laundromat_main); | 6951 | INIT_DELAYED_WORK(&nn->laundromat_work, laundromat_main); |
6950 | get_net(net); | 6952 | get_net(net); |
6951 | 6953 | ||
@@ -7063,14 +7065,14 @@ nfs4_state_shutdown_net(struct net *net) | |||
7063 | } | 7065 | } |
7064 | 7066 | ||
7065 | BUG_ON(!list_empty(&reaplist)); | 7067 | BUG_ON(!list_empty(&reaplist)); |
7066 | spin_lock(&nn->client_lock); | 7068 | spin_lock(&nn->blocked_locks_lock); |
7067 | while (!list_empty(&nn->blocked_locks_lru)) { | 7069 | while (!list_empty(&nn->blocked_locks_lru)) { |
7068 | nbl = list_first_entry(&nn->blocked_locks_lru, | 7070 | nbl = list_first_entry(&nn->blocked_locks_lru, |
7069 | struct nfsd4_blocked_lock, nbl_lru); | 7071 | struct nfsd4_blocked_lock, nbl_lru); |
7070 | list_move(&nbl->nbl_lru, &reaplist); | 7072 | list_move(&nbl->nbl_lru, &reaplist); |
7071 | list_del_init(&nbl->nbl_list); | 7073 | list_del_init(&nbl->nbl_list); |
7072 | } | 7074 | } |
7073 | spin_unlock(&nn->client_lock); | 7075 | spin_unlock(&nn->blocked_locks_lock); |
7074 | 7076 | ||
7075 | while (!list_empty(&reaplist)) { | 7077 | while (!list_empty(&reaplist)) { |
7076 | nbl = list_first_entry(&nn->blocked_locks_lru, | 7078 | nbl = list_first_entry(&nn->blocked_locks_lru, |