[Devel,vz7,3/3] fuse: fuse_writepages_fill must check for FAIL_IMMEDIATELY

Submitted by Maxim Patlasov on Dec. 22, 2016, 2:12 a.m.

Details

Message ID 148237272213.14654.6590589740547003341.stgit@maxim-thinkpad
State New
Series "fuse: some cleanup and fix FUSE_NOTIFY_INVAL_FILES deadlock"
Headers show

Commit Message

Maxim Patlasov Dec. 22, 2016, 2:12 a.m.
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 a519baa..0ffc806 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);