[Devel] [PATCH RHEL7 COMMIT] fuse: fuse_writepages_fill must check for FAIL_IMMEDIATELY
Konstantin Khorenko
khorenko at virtuozzo.com
Tue Jan 17 04:10:57 PST 2017
The commit is pushed to "branch-rh7-3.10.0-514.vz7.27.x-ovz" and will appear at https://src.openvz.org/scm/ovz/vzkernel.git
after rh7-3.10.0-514.vz7.27.12
------>
commit c11cada4b4303f8eb8987f080617a3a6f761c8b3
Author: Maxim Patlasov <mpatlasov at virtuozzo.com>
Date: Tue Jan 17 16:10:57 2017 +0400
fuse: fuse_writepages_fill must check for FAIL_IMMEDIATELY
The patch fixes a deadlock: if a page is stuck under fuse writeback for
a while, and an user re-dirty the page, next writeback will start to wait
for fuse writeback inside fuse_writepages_fill:
> if (fuse_page_is_writeback(inode, page->index)) {
> if (wbc->sync_mode != WB_SYNC_ALL) {
> redirty_page_for_writepage(wbc, page);
> unlock_page(page);
> return 0;
> }
> fuse_wait_on_page_writeback(inode, page->index);
> }
That's correct, but if fuse_invalidate_files steps in now, it will block
in filemap_write_and_wait because fuse_writepages_fill holds page locked.
And even if fused eventually completes initial writeback, it won't be able
to send ACK to the kernel, because /dev/fuse is currently busy by
fuse_invalidate_files. Hence deadlock.
The patch fixes deadlock by waking up fuse_writepages_fill after marking
files with FAIL_IMMEDIATELY flag.
Signed-off-by: Maxim Patlasov <mpatlasov at virtuozzo.com>
---
fs/fuse/file.c | 37 ++++++++++++++++++++++++++++++++-----
fs/fuse/inode.c | 3 +++
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 58313a1..680a437 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -567,17 +567,24 @@ static void fuse_wait_on_page_writeback(struct inode *inode, pgoff_t index)
/*
* Can be woken up by FUSE_NOTIFY_INVAL_FILES
*/
-static void fuse_wait_on_page_writeback_or_invalidate(struct inode *inode,
- struct file *file,
- pgoff_t index)
+static void __fuse_wait_on_page_writeback_or_invalidate(struct inode *inode,
+ struct fuse_file *ff,
+ pgoff_t index)
{
struct fuse_inode *fi = get_fuse_inode(inode);
- struct fuse_file *ff = file->private_data;
wait_event(fi->page_waitq, !fuse_page_is_writeback(inode, index) ||
test_bit(FUSE_S_FAIL_IMMEDIATELY, &ff->ff_state));
}
+static void fuse_wait_on_page_writeback_or_invalidate(struct inode *inode,
+ struct file *file,
+ pgoff_t index)
+{
+ return __fuse_wait_on_page_writeback_or_invalidate(inode,
+ file->private_data, index);
+}
+
static void fuse_wait_on_writeback(struct inode *inode, pgoff_t start,
size_t bytes)
{
@@ -2166,12 +2173,32 @@ static int fuse_writepages_fill(struct page *page,
int check_for_blocked = 0;
if (fuse_page_is_writeback(inode, page->index)) {
+ struct fuse_file *ff;
+
if (wbc->sync_mode != WB_SYNC_ALL) {
redirty_page_for_writepage(wbc, page);
unlock_page(page);
return 0;
}
- fuse_wait_on_page_writeback(inode, page->index);
+
+ /* we can acquire ff here because we do have locked pages here! */
+ ff = fuse_write_file(fc, get_fuse_inode(inode));
+ if (!ff) {
+ printk("FUSE: dirty page on dead file\n");
+ unlock_page(page);
+ return -EIO;
+ }
+
+ /* FUSE_NOTIFY_INVAL_FILES must be able to wake us up */
+ __fuse_wait_on_page_writeback_or_invalidate(inode, ff, page->index);
+
+ if (test_bit(FUSE_S_FAIL_IMMEDIATELY, &ff->ff_state)) {
+ unlock_page(page);
+ fuse_release_ff(inode, ff);
+ return 0;
+ }
+
+ fuse_release_ff(inode, ff);
}
if (req->num_pages &&
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index f606deb..b63aae2 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -413,6 +413,9 @@ int fuse_invalidate_files(struct fuse_conn *fc, u64 nodeid)
/* let them see FUSE_S_FAIL_IMMEDIATELY */
wake_up_all(&fc->blocked_waitq);
+ /* see how fuse_writepages_fill() waits for fuse writeback */
+ wake_up(&fi->page_waitq);
+
err = filemap_write_and_wait(inode->i_mapping);
if (!err || err == -EIO) { /* AS_EIO might trigger -EIO */
spin_lock(&fc->lock);
More information about the Devel
mailing list