[Devel,RHEL7,COMMIT] fuse: fuse_writepages_fill must check for FAIL_IMMEDIATELY

Submitted by Konstantin Khorenko on Jan. 17, 2017, 12:10 p.m.

Details

Message ID 201701171210.v0HCAvRC014287@finist_cl7.x64_64.work.ct
State New
Series "fuse: some cleanup and fix FUSE_NOTIFY_INVAL_FILES deadlock"
Headers show

Commit Message

Konstantin Khorenko Jan. 17, 2017, 12:10 p.m.
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@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@virtuozzo.com>
---
 fs/fuse/file.c  | 37 ++++++++++++++++++++++++++++++++-----
 fs/fuse/inode.c |  3 +++
 2 files changed, 35 insertions(+), 5 deletions(-)

Patch hide | download patch | download mbox

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);