Skip to content

Commit 0858041

Browse files
Mike SnitzerAnna Schumaker
authored andcommitted
nfs_common: track all open nfsd_files per LOCALIO nfs_client
This tracking enables __nfsd_file_cache_purge() to call nfs_localio_invalidate_clients(), upon shutdown or export change, to nfs_close_local_fh() all open nfsd_files that are still cached by the LOCALIO nfs clients associated with nfsd_net that is being shutdown. Now that the client must track all open nfsd_files there was more work than necessary being done with the global nfs_uuids_lock contended. This manifested in various RCU issues, e.g.: hrtimer: interrupt took 47969440 ns rcu: INFO: rcu_sched detected stalls on CPUs/tasks: Use nfs_uuid->lock to protect all nfs_uuid_t members, instead of nfs_uuids_lock, once nfs_uuid_is_local() adds the client to nn->local_clients. Also add 'local_clients_lock' to 'struct nfsd_net' to protect nn->local_clients. And store a pointer to spinlock in the 'list_lock' member of nfs_uuid_t so nfs_localio_disable_client() can use it to avoid taking the global nfs_uuids_lock. In combination, these split out locks eliminate the use of the single nfslocalio.c global nfs_uuids_lock in the IO paths (open and close). Also refactored associated fs/nfs_common/nfslocalio.c methods' locking to reduce work performed with spinlocks held in general. Signed-off-by: Mike Snitzer <[email protected]> Reviewed-by: Jeff Layton <[email protected]> Signed-off-by: Anna Schumaker <[email protected]>
1 parent e1943f4 commit 0858041

File tree

6 files changed

+145
-48
lines changed

6 files changed

+145
-48
lines changed

fs/nfs_common/nfslocalio.c

Lines changed: 125 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,39 +23,65 @@ static DEFINE_SPINLOCK(nfs_uuids_lock);
2323
*/
2424
static LIST_HEAD(nfs_uuids);
2525

26+
/*
27+
* Lock ordering:
28+
* 1: nfs_uuid->lock
29+
* 2: nfs_uuids_lock
30+
* 3: nfs_uuid->list_lock (aka nn->local_clients_lock)
31+
*
32+
* May skip locks in select cases, but never hold multiple
33+
* locks out of order.
34+
*/
35+
2636
void nfs_uuid_init(nfs_uuid_t *nfs_uuid)
2737
{
2838
nfs_uuid->net = NULL;
2939
nfs_uuid->dom = NULL;
40+
nfs_uuid->list_lock = NULL;
3041
INIT_LIST_HEAD(&nfs_uuid->list);
42+
INIT_LIST_HEAD(&nfs_uuid->files);
3143
spin_lock_init(&nfs_uuid->lock);
3244
}
3345
EXPORT_SYMBOL_GPL(nfs_uuid_init);
3446

3547
bool nfs_uuid_begin(nfs_uuid_t *nfs_uuid)
3648
{
49+
spin_lock(&nfs_uuid->lock);
50+
if (nfs_uuid->net) {
51+
/* This nfs_uuid is already in use */
52+
spin_unlock(&nfs_uuid->lock);
53+
return false;
54+
}
55+
3756
spin_lock(&nfs_uuids_lock);
38-
/* Is this nfs_uuid already in use? */
3957
if (!list_empty(&nfs_uuid->list)) {
58+
/* This nfs_uuid is already in use */
4059
spin_unlock(&nfs_uuids_lock);
60+
spin_unlock(&nfs_uuid->lock);
4161
return false;
4262
}
43-
uuid_gen(&nfs_uuid->uuid);
4463
list_add_tail(&nfs_uuid->list, &nfs_uuids);
4564
spin_unlock(&nfs_uuids_lock);
4665

66+
uuid_gen(&nfs_uuid->uuid);
67+
spin_unlock(&nfs_uuid->lock);
68+
4769
return true;
4870
}
4971
EXPORT_SYMBOL_GPL(nfs_uuid_begin);
5072

5173
void nfs_uuid_end(nfs_uuid_t *nfs_uuid)
5274
{
5375
if (nfs_uuid->net == NULL) {
54-
spin_lock(&nfs_uuids_lock);
55-
if (nfs_uuid->net == NULL)
76+
spin_lock(&nfs_uuid->lock);
77+
if (nfs_uuid->net == NULL) {
78+
/* Not local, remove from nfs_uuids */
79+
spin_lock(&nfs_uuids_lock);
5680
list_del_init(&nfs_uuid->list);
57-
spin_unlock(&nfs_uuids_lock);
58-
}
81+
spin_unlock(&nfs_uuids_lock);
82+
}
83+
spin_unlock(&nfs_uuid->lock);
84+
}
5985
}
6086
EXPORT_SYMBOL_GPL(nfs_uuid_end);
6187

@@ -73,28 +99,39 @@ static nfs_uuid_t * nfs_uuid_lookup_locked(const uuid_t *uuid)
7399
static struct module *nfsd_mod;
74100

75101
void nfs_uuid_is_local(const uuid_t *uuid, struct list_head *list,
76-
struct net *net, struct auth_domain *dom,
77-
struct module *mod)
102+
spinlock_t *list_lock, struct net *net,
103+
struct auth_domain *dom, struct module *mod)
78104
{
79105
nfs_uuid_t *nfs_uuid;
80106

81107
spin_lock(&nfs_uuids_lock);
82108
nfs_uuid = nfs_uuid_lookup_locked(uuid);
83-
if (nfs_uuid) {
84-
kref_get(&dom->ref);
85-
nfs_uuid->dom = dom;
86-
/*
87-
* We don't hold a ref on the net, but instead put
88-
* ourselves on a list so the net pointer can be
89-
* invalidated.
90-
*/
91-
list_move(&nfs_uuid->list, list);
92-
rcu_assign_pointer(nfs_uuid->net, net);
93-
94-
__module_get(mod);
95-
nfsd_mod = mod;
109+
if (!nfs_uuid) {
110+
spin_unlock(&nfs_uuids_lock);
111+
return;
96112
}
113+
114+
/*
115+
* We don't hold a ref on the net, but instead put
116+
* ourselves on @list (nn->local_clients) so the net
117+
* pointer can be invalidated.
118+
*/
119+
spin_lock(list_lock); /* list_lock is nn->local_clients_lock */
120+
list_move(&nfs_uuid->list, list);
121+
spin_unlock(list_lock);
122+
97123
spin_unlock(&nfs_uuids_lock);
124+
/* Once nfs_uuid is parented to @list, avoid global nfs_uuids_lock */
125+
spin_lock(&nfs_uuid->lock);
126+
127+
__module_get(mod);
128+
nfsd_mod = mod;
129+
130+
nfs_uuid->list_lock = list_lock;
131+
kref_get(&dom->ref);
132+
nfs_uuid->dom = dom;
133+
rcu_assign_pointer(nfs_uuid->net, net);
134+
spin_unlock(&nfs_uuid->lock);
98135
}
99136
EXPORT_SYMBOL_GPL(nfs_uuid_is_local);
100137

@@ -108,55 +145,96 @@ void nfs_localio_enable_client(struct nfs_client *clp)
108145
}
109146
EXPORT_SYMBOL_GPL(nfs_localio_enable_client);
110147

111-
static void nfs_uuid_put_locked(nfs_uuid_t *nfs_uuid)
148+
/*
149+
* Cleanup the nfs_uuid_t embedded in an nfs_client.
150+
* This is the long-form of nfs_uuid_init().
151+
*/
152+
static void nfs_uuid_put(nfs_uuid_t *nfs_uuid)
112153
{
113-
if (!nfs_uuid->net)
154+
LIST_HEAD(local_files);
155+
struct nfs_file_localio *nfl, *tmp;
156+
157+
spin_lock(&nfs_uuid->lock);
158+
if (unlikely(!nfs_uuid->net)) {
159+
spin_unlock(&nfs_uuid->lock);
114160
return;
115-
module_put(nfsd_mod);
161+
}
116162
RCU_INIT_POINTER(nfs_uuid->net, NULL);
117163

118164
if (nfs_uuid->dom) {
119165
auth_domain_put(nfs_uuid->dom);
120166
nfs_uuid->dom = NULL;
121167
}
122-
list_del_init(&nfs_uuid->list);
168+
169+
list_splice_init(&nfs_uuid->files, &local_files);
170+
spin_unlock(&nfs_uuid->lock);
171+
172+
/* Walk list of files and ensure their last references dropped */
173+
list_for_each_entry_safe(nfl, tmp, &local_files, list) {
174+
nfs_close_local_fh(nfl);
175+
cond_resched();
176+
}
177+
178+
spin_lock(&nfs_uuid->lock);
179+
BUG_ON(!list_empty(&nfs_uuid->files));
180+
181+
/* Remove client from nn->local_clients */
182+
if (nfs_uuid->list_lock) {
183+
spin_lock(nfs_uuid->list_lock);
184+
BUG_ON(list_empty(&nfs_uuid->list));
185+
list_del_init(&nfs_uuid->list);
186+
spin_unlock(nfs_uuid->list_lock);
187+
nfs_uuid->list_lock = NULL;
188+
}
189+
190+
module_put(nfsd_mod);
191+
spin_unlock(&nfs_uuid->lock);
123192
}
124193

125194
void nfs_localio_disable_client(struct nfs_client *clp)
126195
{
127-
nfs_uuid_t *nfs_uuid = &clp->cl_uuid;
196+
nfs_uuid_t *nfs_uuid = NULL;
128197

129-
spin_lock(&nfs_uuid->lock);
198+
spin_lock(&clp->cl_uuid.lock); /* aka &nfs_uuid->lock */
130199
if (test_and_clear_bit(NFS_CS_LOCAL_IO, &clp->cl_flags)) {
131-
spin_lock(&nfs_uuids_lock);
132-
nfs_uuid_put_locked(nfs_uuid);
133-
spin_unlock(&nfs_uuids_lock);
200+
/* &clp->cl_uuid is always not NULL, using as bool here */
201+
nfs_uuid = &clp->cl_uuid;
134202
}
135-
spin_unlock(&nfs_uuid->lock);
203+
spin_unlock(&clp->cl_uuid.lock);
204+
205+
if (nfs_uuid)
206+
nfs_uuid_put(nfs_uuid);
136207
}
137208
EXPORT_SYMBOL_GPL(nfs_localio_disable_client);
138209

139-
void nfs_localio_invalidate_clients(struct list_head *cl_uuid_list)
210+
void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
211+
spinlock_t *nn_local_clients_lock)
140212
{
213+
LIST_HEAD(local_clients);
141214
nfs_uuid_t *nfs_uuid, *tmp;
142-
143-
spin_lock(&nfs_uuids_lock);
144-
list_for_each_entry_safe(nfs_uuid, tmp, cl_uuid_list, list) {
145-
struct nfs_client *clp =
146-
container_of(nfs_uuid, struct nfs_client, cl_uuid);
147-
215+
struct nfs_client *clp;
216+
217+
spin_lock(nn_local_clients_lock);
218+
list_splice_init(nn_local_clients, &local_clients);
219+
spin_unlock(nn_local_clients_lock);
220+
list_for_each_entry_safe(nfs_uuid, tmp, &local_clients, list) {
221+
if (WARN_ON(nfs_uuid->list_lock != nn_local_clients_lock))
222+
break;
223+
clp = container_of(nfs_uuid, struct nfs_client, cl_uuid);
148224
nfs_localio_disable_client(clp);
149225
}
150-
spin_unlock(&nfs_uuids_lock);
151226
}
152227
EXPORT_SYMBOL_GPL(nfs_localio_invalidate_clients);
153228

154229
static void nfs_uuid_add_file(nfs_uuid_t *nfs_uuid, struct nfs_file_localio *nfl)
155230
{
156-
spin_lock(&nfs_uuids_lock);
157-
if (!nfl->nfs_uuid)
231+
/* Add nfl to nfs_uuid->files if it isn't already */
232+
spin_lock(&nfs_uuid->lock);
233+
if (list_empty(&nfl->list)) {
158234
rcu_assign_pointer(nfl->nfs_uuid, nfs_uuid);
159-
spin_unlock(&nfs_uuids_lock);
235+
list_add_tail(&nfl->list, &nfs_uuid->files);
236+
}
237+
spin_unlock(&nfs_uuid->lock);
160238
}
161239

162240
/*
@@ -217,14 +295,16 @@ void nfs_close_local_fh(struct nfs_file_localio *nfl)
217295
ro_nf = rcu_access_pointer(nfl->ro_file);
218296
rw_nf = rcu_access_pointer(nfl->rw_file);
219297
if (ro_nf || rw_nf) {
220-
spin_lock(&nfs_uuids_lock);
298+
spin_lock(&nfs_uuid->lock);
221299
if (ro_nf)
222300
ro_nf = rcu_dereference_protected(xchg(&nfl->ro_file, NULL), 1);
223301
if (rw_nf)
224302
rw_nf = rcu_dereference_protected(xchg(&nfl->rw_file, NULL), 1);
225303

226-
rcu_assign_pointer(nfl->nfs_uuid, NULL);
227-
spin_unlock(&nfs_uuids_lock);
304+
/* Remove nfl from nfs_uuid->files list */
305+
RCU_INIT_POINTER(nfl->nfs_uuid, NULL);
306+
list_del_init(&nfl->list);
307+
spin_unlock(&nfs_uuid->lock);
228308
rcu_read_unlock();
229309

230310
if (ro_nf)

fs/nfsd/filecache.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <linux/fsnotify.h>
4040
#include <linux/seq_file.h>
4141
#include <linux/rhashtable.h>
42+
#include <linux/nfslocalio.h>
4243

4344
#include "vfs.h"
4445
#include "nfsd.h"
@@ -833,6 +834,14 @@ __nfsd_file_cache_purge(struct net *net)
833834
struct nfsd_file *nf;
834835
LIST_HEAD(dispose);
835836

837+
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
838+
if (net) {
839+
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
840+
nfs_localio_invalidate_clients(&nn->local_clients,
841+
&nn->local_clients_lock);
842+
}
843+
#endif
844+
836845
rhltable_walk_enter(&nfsd_file_rhltable, &iter);
837846
do {
838847
rhashtable_walk_start(&iter);

fs/nfsd/localio.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp)
116116
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
117117

118118
nfs_uuid_is_local(&argp->uuid, &nn->local_clients,
119+
&nn->local_clients_lock,
119120
net, rqstp->rq_client, THIS_MODULE);
120121

121122
return rpc_success;

fs/nfsd/netns.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ struct nfsd_net {
220220

221221
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
222222
/* Local clients to be invalidated when net is shut down */
223+
spinlock_t local_clients_lock;
223224
struct list_head local_clients;
224225
#endif
225226
};

fs/nfsd/nfsctl.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2259,6 +2259,7 @@ static __net_init int nfsd_net_init(struct net *net)
22592259
seqlock_init(&nn->writeverf_lock);
22602260
nfsd_proc_stat_init(net);
22612261
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
2262+
spin_lock_init(&nn->local_clients_lock);
22622263
INIT_LIST_HEAD(&nn->local_clients);
22632264
#endif
22642265
return 0;
@@ -2283,7 +2284,8 @@ static __net_exit void nfsd_net_pre_exit(struct net *net)
22832284
{
22842285
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
22852286

2286-
nfs_localio_invalidate_clients(&nn->local_clients);
2287+
nfs_localio_invalidate_clients(&nn->local_clients,
2288+
&nn->local_clients_lock);
22872289
}
22882290
#endif
22892291

include/linux/nfslocalio.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,23 @@ typedef struct {
3030
/* sadly this struct is just over a cacheline, avoid bouncing */
3131
spinlock_t ____cacheline_aligned lock;
3232
struct list_head list;
33+
spinlock_t *list_lock; /* nn->local_clients_lock */
3334
struct net __rcu *net; /* nfsd's network namespace */
3435
struct auth_domain *dom; /* auth_domain for localio */
36+
/* Local files to close when net is shut down or exports change */
37+
struct list_head files;
3538
} nfs_uuid_t;
3639

3740
void nfs_uuid_init(nfs_uuid_t *);
3841
bool nfs_uuid_begin(nfs_uuid_t *);
3942
void nfs_uuid_end(nfs_uuid_t *);
40-
void nfs_uuid_is_local(const uuid_t *, struct list_head *,
43+
void nfs_uuid_is_local(const uuid_t *, struct list_head *, spinlock_t *,
4144
struct net *, struct auth_domain *, struct module *);
4245

4346
void nfs_localio_enable_client(struct nfs_client *clp);
4447
void nfs_localio_disable_client(struct nfs_client *clp);
45-
void nfs_localio_invalidate_clients(struct list_head *list);
48+
void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
49+
spinlock_t *nn_local_clients_lock);
4650

4751
/* localio needs to map filehandle -> struct nfsd_file */
4852
extern struct nfsd_file *

0 commit comments

Comments
 (0)