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 /fs/nfs/write.c | |
parent | 196639ebbe63a037fe9a80669140bd292d8bcd80 (diff) |
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>
Diffstat (limited to 'fs/nfs/write.c')
-rw-r--r-- | fs/nfs/write.c | 15 |
1 files changed, 11 insertions, 4 deletions
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 | |||
1028 | nfs_scan_commit_list(struct list_head *src, struct list_head *dst, | 1028 | nfs_scan_commit_list(struct list_head *src, struct list_head *dst, |
1029 | struct nfs_commit_info *cinfo, int max) | 1029 | struct nfs_commit_info *cinfo, int max) |
1030 | { | 1030 | { |
1031 | struct nfs_page *req; | 1031 | struct nfs_page *req, *tmp; |
1032 | int ret = 0; | 1032 | int ret = 0; |
1033 | 1033 | ||
1034 | while(!list_empty(src)) { | 1034 | restart: |
1035 | req = list_first_entry(src, struct nfs_page, wb_list); | 1035 | list_for_each_entry_safe(req, tmp, src, wb_list) { |
1036 | kref_get(&req->wb_kref); | 1036 | kref_get(&req->wb_kref); |
1037 | if (!nfs_lock_request(req)) { | 1037 | if (!nfs_lock_request(req)) { |
1038 | int status; | 1038 | int status; |
1039 | |||
1040 | /* Prevent deadlock with nfs_lock_and_join_requests */ | ||
1041 | if (!list_empty(dst)) { | ||
1042 | nfs_release_request(req); | ||
1043 | continue; | ||
1044 | } | ||
1045 | /* Ensure we make progress to prevent livelock */ | ||
1039 | mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); | 1046 | mutex_unlock(&NFS_I(cinfo->inode)->commit_mutex); |
1040 | status = nfs_wait_on_request(req); | 1047 | status = nfs_wait_on_request(req); |
1041 | nfs_release_request(req); | 1048 | nfs_release_request(req); |
1042 | mutex_lock(&NFS_I(cinfo->inode)->commit_mutex); | 1049 | mutex_lock(&NFS_I(cinfo->inode)->commit_mutex); |
1043 | if (status < 0) | 1050 | if (status < 0) |
1044 | break; | 1051 | break; |
1045 | continue; | 1052 | goto restart; |
1046 | } | 1053 | } |
1047 | nfs_request_remove_commit_list(req, cinfo); | 1054 | nfs_request_remove_commit_list(req, cinfo); |
1048 | nfs_list_add_request(req, dst); | 1055 | nfs_list_add_request(req, dst); |