Skip to content

Commit 08ca8b2

Browse files
author
Trond Myklebust
committed
NFS: Fix races nfs_page_group_destroy() vs nfs_destroy_unlinked_subrequests()
When a subrequest is being detached from the subgroup, we want to ensure that it is not holding the group lock, or in the process of waiting for the group lock. Fixes: 5b2b518 ("NFS: Fix nfs_page_group_destroy() and nfs_lock_and_join_requests() race cases") Signed-off-by: Trond Myklebust <[email protected]>
1 parent add42de commit 08ca8b2

File tree

3 files changed

+55
-24
lines changed

3 files changed

+55
-24
lines changed

fs/nfs/pagelist.c

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,47 +133,70 @@ nfs_async_iocounter_wait(struct rpc_task *task, struct nfs_lock_context *l_ctx)
133133
EXPORT_SYMBOL_GPL(nfs_async_iocounter_wait);
134134

135135
/*
136-
* nfs_page_group_lock - lock the head of the page group
137-
* @req - request in group that is to be locked
136+
* nfs_page_set_headlock - set the request PG_HEADLOCK
137+
* @req: request that is to be locked
138138
*
139-
* this lock must be held when traversing or modifying the page
140-
* group list
139+
* this lock must be held when modifying req->wb_head
141140
*
142141
* return 0 on success, < 0 on error
143142
*/
144143
int
145-
nfs_page_group_lock(struct nfs_page *req)
144+
nfs_page_set_headlock(struct nfs_page *req)
146145
{
147-
struct nfs_page *head = req->wb_head;
148-
149-
WARN_ON_ONCE(head != head->wb_head);
150-
151-
if (!test_and_set_bit(PG_HEADLOCK, &head->wb_flags))
146+
if (!test_and_set_bit(PG_HEADLOCK, &req->wb_flags))
152147
return 0;
153148

154-
set_bit(PG_CONTENDED1, &head->wb_flags);
149+
set_bit(PG_CONTENDED1, &req->wb_flags);
155150
smp_mb__after_atomic();
156-
return wait_on_bit_lock(&head->wb_flags, PG_HEADLOCK,
151+
return wait_on_bit_lock(&req->wb_flags, PG_HEADLOCK,
157152
TASK_UNINTERRUPTIBLE);
158153
}
159154

160155
/*
161-
* nfs_page_group_unlock - unlock the head of the page group
162-
* @req - request in group that is to be unlocked
156+
* nfs_page_clear_headlock - clear the request PG_HEADLOCK
157+
* @req: request that is to be locked
163158
*/
164159
void
165-
nfs_page_group_unlock(struct nfs_page *req)
160+
nfs_page_clear_headlock(struct nfs_page *req)
166161
{
167-
struct nfs_page *head = req->wb_head;
168-
169-
WARN_ON_ONCE(head != head->wb_head);
170-
171162
smp_mb__before_atomic();
172-
clear_bit(PG_HEADLOCK, &head->wb_flags);
163+
clear_bit(PG_HEADLOCK, &req->wb_flags);
173164
smp_mb__after_atomic();
174-
if (!test_bit(PG_CONTENDED1, &head->wb_flags))
165+
if (!test_bit(PG_CONTENDED1, &req->wb_flags))
175166
return;
176-
wake_up_bit(&head->wb_flags, PG_HEADLOCK);
167+
wake_up_bit(&req->wb_flags, PG_HEADLOCK);
168+
}
169+
170+
/*
171+
* nfs_page_group_lock - lock the head of the page group
172+
* @req: request in group that is to be locked
173+
*
174+
* this lock must be held when traversing or modifying the page
175+
* group list
176+
*
177+
* return 0 on success, < 0 on error
178+
*/
179+
int
180+
nfs_page_group_lock(struct nfs_page *req)
181+
{
182+
int ret;
183+
184+
ret = nfs_page_set_headlock(req);
185+
if (ret || req->wb_head == req)
186+
return ret;
187+
return nfs_page_set_headlock(req->wb_head);
188+
}
189+
190+
/*
191+
* nfs_page_group_unlock - unlock the head of the page group
192+
* @req: request in group that is to be unlocked
193+
*/
194+
void
195+
nfs_page_group_unlock(struct nfs_page *req)
196+
{
197+
if (req != req->wb_head)
198+
nfs_page_clear_headlock(req->wb_head);
199+
nfs_page_clear_headlock(req);
177200
}
178201

179202
/*

fs/nfs/write.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,22 +428,28 @@ nfs_destroy_unlinked_subrequests(struct nfs_page *destroy_list,
428428
destroy_list = (subreq->wb_this_page == old_head) ?
429429
NULL : subreq->wb_this_page;
430430

431+
/* Note: lock subreq in order to change subreq->wb_head */
432+
nfs_page_set_headlock(subreq);
431433
WARN_ON_ONCE(old_head != subreq->wb_head);
432434

433435
/* make sure old group is not used */
434436
subreq->wb_this_page = subreq;
437+
subreq->wb_head = subreq;
435438

436439
clear_bit(PG_REMOVE, &subreq->wb_flags);
437440

438441
/* Note: races with nfs_page_group_destroy() */
439442
if (!kref_read(&subreq->wb_kref)) {
440443
/* Check if we raced with nfs_page_group_destroy() */
441-
if (test_and_clear_bit(PG_TEARDOWN, &subreq->wb_flags))
444+
if (test_and_clear_bit(PG_TEARDOWN, &subreq->wb_flags)) {
445+
nfs_page_clear_headlock(subreq);
442446
nfs_free_request(subreq);
447+
} else
448+
nfs_page_clear_headlock(subreq);
443449
continue;
444450
}
451+
nfs_page_clear_headlock(subreq);
445452

446-
subreq->wb_head = subreq;
447453
nfs_release_request(old_head);
448454

449455
if (test_and_clear_bit(PG_INODE_REF, &subreq->wb_flags)) {

include/linux/nfs_page.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ extern void nfs_unlock_and_release_request(struct nfs_page *);
142142
extern int nfs_page_group_lock(struct nfs_page *);
143143
extern void nfs_page_group_unlock(struct nfs_page *);
144144
extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int);
145+
extern int nfs_page_set_headlock(struct nfs_page *req);
146+
extern void nfs_page_clear_headlock(struct nfs_page *req);
145147
extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *);
146148

147149
/*

0 commit comments

Comments
 (0)