NFS: Fix races nfs_page_group_destroy() vs nfs_destroy_unlinked_subrequests()
[ Upstream commit 08ca8b21f760c0ed5034a5c122092eec22ccf8f4 ]
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: 5b2b5187fa
("NFS: Fix nfs_page_group_destroy() and nfs_lock_and_join_requests() race cases")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
4667358dab
commit
24c56b6fe0
@ -132,9 +132,44 @@ nfs_async_iocounter_wait(struct rpc_task *task, struct nfs_lock_context *l_ctx)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(nfs_async_iocounter_wait);
|
EXPORT_SYMBOL_GPL(nfs_async_iocounter_wait);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* nfs_page_set_headlock - set the request PG_HEADLOCK
|
||||||
|
* @req: request that is to be locked
|
||||||
|
*
|
||||||
|
* this lock must be held when modifying req->wb_head
|
||||||
|
*
|
||||||
|
* return 0 on success, < 0 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
nfs_page_set_headlock(struct nfs_page *req)
|
||||||
|
{
|
||||||
|
if (!test_and_set_bit(PG_HEADLOCK, &req->wb_flags))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
set_bit(PG_CONTENDED1, &req->wb_flags);
|
||||||
|
smp_mb__after_atomic();
|
||||||
|
return wait_on_bit_lock(&req->wb_flags, PG_HEADLOCK,
|
||||||
|
TASK_UNINTERRUPTIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* nfs_page_clear_headlock - clear the request PG_HEADLOCK
|
||||||
|
* @req: request that is to be locked
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
nfs_page_clear_headlock(struct nfs_page *req)
|
||||||
|
{
|
||||||
|
smp_mb__before_atomic();
|
||||||
|
clear_bit(PG_HEADLOCK, &req->wb_flags);
|
||||||
|
smp_mb__after_atomic();
|
||||||
|
if (!test_bit(PG_CONTENDED1, &req->wb_flags))
|
||||||
|
return;
|
||||||
|
wake_up_bit(&req->wb_flags, PG_HEADLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* nfs_page_group_lock - lock the head of the page group
|
* nfs_page_group_lock - lock the head of the page group
|
||||||
* @req - request in group that is to be locked
|
* @req: request in group that is to be locked
|
||||||
*
|
*
|
||||||
* this lock must be held when traversing or modifying the page
|
* this lock must be held when traversing or modifying the page
|
||||||
* group list
|
* group list
|
||||||
@ -144,36 +179,24 @@ EXPORT_SYMBOL_GPL(nfs_async_iocounter_wait);
|
|||||||
int
|
int
|
||||||
nfs_page_group_lock(struct nfs_page *req)
|
nfs_page_group_lock(struct nfs_page *req)
|
||||||
{
|
{
|
||||||
struct nfs_page *head = req->wb_head;
|
int ret;
|
||||||
|
|
||||||
WARN_ON_ONCE(head != head->wb_head);
|
ret = nfs_page_set_headlock(req);
|
||||||
|
if (ret || req->wb_head == req)
|
||||||
if (!test_and_set_bit(PG_HEADLOCK, &head->wb_flags))
|
return ret;
|
||||||
return 0;
|
return nfs_page_set_headlock(req->wb_head);
|
||||||
|
|
||||||
set_bit(PG_CONTENDED1, &head->wb_flags);
|
|
||||||
smp_mb__after_atomic();
|
|
||||||
return wait_on_bit_lock(&head->wb_flags, PG_HEADLOCK,
|
|
||||||
TASK_UNINTERRUPTIBLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* nfs_page_group_unlock - unlock the head of the page group
|
* nfs_page_group_unlock - unlock the head of the page group
|
||||||
* @req - request in group that is to be unlocked
|
* @req: request in group that is to be unlocked
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
nfs_page_group_unlock(struct nfs_page *req)
|
nfs_page_group_unlock(struct nfs_page *req)
|
||||||
{
|
{
|
||||||
struct nfs_page *head = req->wb_head;
|
if (req != req->wb_head)
|
||||||
|
nfs_page_clear_headlock(req->wb_head);
|
||||||
WARN_ON_ONCE(head != head->wb_head);
|
nfs_page_clear_headlock(req);
|
||||||
|
|
||||||
smp_mb__before_atomic();
|
|
||||||
clear_bit(PG_HEADLOCK, &head->wb_flags);
|
|
||||||
smp_mb__after_atomic();
|
|
||||||
if (!test_bit(PG_CONTENDED1, &head->wb_flags))
|
|
||||||
return;
|
|
||||||
wake_up_bit(&head->wb_flags, PG_HEADLOCK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -425,22 +425,28 @@ nfs_destroy_unlinked_subrequests(struct nfs_page *destroy_list,
|
|||||||
destroy_list = (subreq->wb_this_page == old_head) ?
|
destroy_list = (subreq->wb_this_page == old_head) ?
|
||||||
NULL : subreq->wb_this_page;
|
NULL : subreq->wb_this_page;
|
||||||
|
|
||||||
|
/* Note: lock subreq in order to change subreq->wb_head */
|
||||||
|
nfs_page_set_headlock(subreq);
|
||||||
WARN_ON_ONCE(old_head != subreq->wb_head);
|
WARN_ON_ONCE(old_head != subreq->wb_head);
|
||||||
|
|
||||||
/* make sure old group is not used */
|
/* make sure old group is not used */
|
||||||
subreq->wb_this_page = subreq;
|
subreq->wb_this_page = subreq;
|
||||||
|
subreq->wb_head = subreq;
|
||||||
|
|
||||||
clear_bit(PG_REMOVE, &subreq->wb_flags);
|
clear_bit(PG_REMOVE, &subreq->wb_flags);
|
||||||
|
|
||||||
/* Note: races with nfs_page_group_destroy() */
|
/* Note: races with nfs_page_group_destroy() */
|
||||||
if (!kref_read(&subreq->wb_kref)) {
|
if (!kref_read(&subreq->wb_kref)) {
|
||||||
/* Check if we raced with nfs_page_group_destroy() */
|
/* Check if we raced with nfs_page_group_destroy() */
|
||||||
if (test_and_clear_bit(PG_TEARDOWN, &subreq->wb_flags))
|
if (test_and_clear_bit(PG_TEARDOWN, &subreq->wb_flags)) {
|
||||||
|
nfs_page_clear_headlock(subreq);
|
||||||
nfs_free_request(subreq);
|
nfs_free_request(subreq);
|
||||||
|
} else
|
||||||
|
nfs_page_clear_headlock(subreq);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
nfs_page_clear_headlock(subreq);
|
||||||
|
|
||||||
subreq->wb_head = subreq;
|
|
||||||
nfs_release_request(old_head);
|
nfs_release_request(old_head);
|
||||||
|
|
||||||
if (test_and_clear_bit(PG_INODE_REF, &subreq->wb_flags)) {
|
if (test_and_clear_bit(PG_INODE_REF, &subreq->wb_flags)) {
|
||||||
|
@ -142,6 +142,8 @@ extern void nfs_unlock_and_release_request(struct nfs_page *);
|
|||||||
extern int nfs_page_group_lock(struct nfs_page *);
|
extern int nfs_page_group_lock(struct nfs_page *);
|
||||||
extern void nfs_page_group_unlock(struct nfs_page *);
|
extern void nfs_page_group_unlock(struct nfs_page *);
|
||||||
extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int);
|
extern bool nfs_page_group_sync_on_bit(struct nfs_page *, unsigned int);
|
||||||
|
extern int nfs_page_set_headlock(struct nfs_page *req);
|
||||||
|
extern void nfs_page_clear_headlock(struct nfs_page *req);
|
||||||
extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *);
|
extern bool nfs_async_iocounter_wait(struct rpc_task *, struct nfs_lock_context *);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
Reference in New Issue
Block a user