diff options
author | Trond Myklebust <trond.myklebust@primarydata.com> | 2017-09-09 12:28:01 -0400 |
---|---|---|
committer | Trond Myklebust <trond.myklebust@primarydata.com> | 2017-09-09 12:28:01 -0400 |
commit | 137da553dba62dfc64fb8f4ccb5be769acbf615e (patch) | |
tree | 18e8bb3b882b37c585e0b498784fe627da90481a | |
parent | 196639ebbe63a037fe9a80669140bd292d8bcd80 (diff) | |
download | linux-137da553dba62dfc64fb8f4ccb5be769acbf615e.tar.gz linux-137da553dba62dfc64fb8f4ccb5be769acbf615e.tar.bz2 linux-137da553dba62dfc64fb8f4ccb5be769acbf615e.zip |
NFS: nfs_lock_and_join_requests and nfs_scan_commit_list can deadlock
Since the commit list is not ordered, it is possible for nfs_scan_commit_list
to hold a request that nfs_lock_and_join_requests() is waiting for, while
at the same time trying to grab a request that nfs_lock_and_join_requests
already holds.
Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
-rw-r--r-- | fs/nfs/pnfs_nfs.c | 16 | ||||
-rw-r--r-- | fs/nfs/write.c | 15 |
2 files changed, 22 insertions, 9 deletions
diff --git a/fs/nfs/pnfs_nfs.c b/fs/nfs/pnfs_nfs.c index 303ff171cb5d..d03d836b6ee0 100644 --- a/fs/nfs/pnfs_nfs.c +++ b/fs/nfs/pnfs_nfs.c @@ -91,22 +91,28 @@ static int pnfs_generic_transfer_commit_list(struct list_head *src, struct list_head *dst, struct nfs_commit_info *cinfo, int max) { - struct nfs_page *req; + struct nfs_page *req, *tmp; int ret = 0; - while(!list_empty(src)) { - req = list_first_entry(src, struct nfs_page, wb_list); - +restart: + list_for_each_entry_safe(req, tmp, src, wb_list) { kref_get(&req->wb_kref); if (!nfs_lock_request(req)) { int status; + + /* Prevent deadlock with nfs_lock_and_join_requests */ + if (!list_empty(dst)) { + nfs_release_request(req); + continue; + } + /* Ensure we make progress to prevent livelock */ mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); status = nfs_wait_on_request(req); nfs_release_request(req); mutex_lock(&NFS_I(cinfo->inode)->commit_mutex); if (status < 0) break; - continue; + goto restart; } nfs_request_remove_commit_list(req, cinfo); clear_bit(PG_COMMIT_TO_DS, &req->wb_flags); diff --git a/fs/nfs/write.c b/fs/nfs/write.c index ae26775b5448..c3f627b08ec6 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -1028,21 +1028,28 @@ int nfs_scan_commit_list(struct list_head *src, struct list_head *dst, struct nfs_commit_info *cinfo, int max) { - struct nfs_page *req; + struct nfs_page *req, *tmp; int ret = 0; - while(!list_empty(src)) { - req = list_first_entry(src, struct nfs_page, wb_list); +restart: + list_for_each_entry_safe(req, tmp, src, wb_list) { kref_get(&req->wb_kref); if (!nfs_lock_request(req)) { int status; + + /* Prevent deadlock with nfs_lock_and_join_requests */ + if (!list_empty(dst)) { + nfs_release_request(req); + continue; + } + /* Ensure we make progress to prevent livelock */ mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); status = nfs_wait_on_request(req); nfs_release_request(req); mutex_lock(&NFS_I(cinfo->inode)->commit_mutex); if (status < 0) break; - continue; + goto restart; } nfs_request_remove_commit_list(req, cinfo); nfs_list_add_request(req, dst); |