diff options
author | Jeff Layton <jlayton@redhat.com> | 2011-10-12 10:57:42 -0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2011-10-18 12:08:11 -0400 |
commit | 2da956523526e440ef4f4dd174e26f5ac06fe011 (patch) | |
tree | 718d8edd26d3eb59727dea97075ff59ba4d068fa /fs/nfs/write.c | |
parent | b9dd3abbbc708da5e3c53424a5b2c66ab580f97e (diff) |
nfs: don't try to migrate pages with active requests
nfs_find_and_lock_request will take a reference to the nfs_page and
will then put it if the req is already locked. It's possible though
that the reference will be the last one. That put then can kick off
a whole series of reference puts:
nfs_page
nfs_open_context
dentry
inode
If the inode ends up being deleted, then the VFS will call
truncate_inode_pages. That function will try to take the page lock, but
it was already locked when migrate_page was called. The code
deadlocks.
Fix this by simply refusing the migration request if PagePrivate is
already set, indicating that the page is already associated with an
active read or write request.
We've had a customer test a backported version of this patch and
the preliminary results seem good.
Cc: stable@kernel.org
Cc: Andrea Arcangeli <aarcange@redhat.com>
Reported-by: Harshula Jayasuriya <harshula@redhat.com>
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs/write.c')
-rw-r--r-- | fs/nfs/write.c | 36 |
1 files changed, 11 insertions, 25 deletions
diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 46aa4389ce13..72813ede029e 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c | |||
@@ -1691,34 +1691,20 @@ out_error: | |||
1691 | int nfs_migrate_page(struct address_space *mapping, struct page *newpage, | 1691 | int nfs_migrate_page(struct address_space *mapping, struct page *newpage, |
1692 | struct page *page) | 1692 | struct page *page) |
1693 | { | 1693 | { |
1694 | struct nfs_page *req; | 1694 | /* |
1695 | int ret; | 1695 | * If PagePrivate is set, then the page is currently associated with |
1696 | * an in-progress read or write request. Don't try to migrate it. | ||
1697 | * | ||
1698 | * FIXME: we could do this in principle, but we'll need a way to ensure | ||
1699 | * that we can safely release the inode reference while holding | ||
1700 | * the page lock. | ||
1701 | */ | ||
1702 | if (PagePrivate(page)) | ||
1703 | return -EBUSY; | ||
1696 | 1704 | ||
1697 | nfs_fscache_release_page(page, GFP_KERNEL); | 1705 | nfs_fscache_release_page(page, GFP_KERNEL); |
1698 | 1706 | ||
1699 | req = nfs_find_and_lock_request(page, false); | 1707 | return migrate_page(mapping, newpage, page); |
1700 | ret = PTR_ERR(req); | ||
1701 | if (IS_ERR(req)) | ||
1702 | goto out; | ||
1703 | |||
1704 | ret = migrate_page(mapping, newpage, page); | ||
1705 | if (!req) | ||
1706 | goto out; | ||
1707 | if (ret) | ||
1708 | goto out_unlock; | ||
1709 | page_cache_get(newpage); | ||
1710 | spin_lock(&mapping->host->i_lock); | ||
1711 | req->wb_page = newpage; | ||
1712 | SetPagePrivate(newpage); | ||
1713 | set_page_private(newpage, (unsigned long)req); | ||
1714 | ClearPagePrivate(page); | ||
1715 | set_page_private(page, 0); | ||
1716 | spin_unlock(&mapping->host->i_lock); | ||
1717 | page_cache_release(page); | ||
1718 | out_unlock: | ||
1719 | nfs_clear_page_tag_locked(req); | ||
1720 | out: | ||
1721 | return ret; | ||
1722 | } | 1708 | } |
1723 | #endif | 1709 | #endif |
1724 | 1710 | ||