The patch fixes another race dealing with fuse_invalidate_files, this time when it races with truncate(2):
Thread A: the flusher performs writeback as usual: fuse_writepages --> fuse_send_writepages --> end_page_writeback but before fuse_send_writepages acquires fc->lock and calls fuse_flush_writepages, some innocent user process re-dirty-es the page. Thread B: truncate(2) attempts to truncate (shrink) file as usual: fuse_do_setattr --> invalidate_inode_pages2 (This is possible because Thread A has not incremented fi->writectr yet.) But invalidate_inode_pages2 finds that re-dirty-ed page and sticks in: invalidate_inode_pages2 --> fuse_launder_page --> fuse_writepage_locked --> fuse_wait_on_page_writeback Thread A: the flusher proceeds with fuse_flush_writepages, sends write request to userspace fuse daemon, but the daemon is not obliged to fulfill it immediately. So, thread B waits now for thread A, while thread A waits for userspace. Now fuse_invalidate_files steps in sticking in filemap_write_and_wait on the page locked by Thread B (launder_page always work on a locked page). Deadlock. The patch fixes deadlock by waking up fuse_writepage_locked after marking files with FAIL_IMMEDIATELY flag. Changed in v2: - instead of flagging "fail_immediately", let fuse_writepage_locked return fuse_file pointer, then the caller (fuse_launder_page) can use it for conditional wait on __fuse_wait_on_page_writeback_or_invalidate. This is important because otherwise fuse_invalidate_files may deadlock when launder waits for fuse writeback. Signed-off-by: Maxim Patlasov <mpatla...@virtuozzo.com> --- fs/fuse/file.c | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 0ffc806..34e75c2 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1963,7 +1963,8 @@ static struct fuse_file *fuse_write_file(struct fuse_conn *fc, } static int fuse_writepage_locked(struct page *page, - struct writeback_control *wbc) + struct writeback_control *wbc, + struct fuse_file **ff_pp) { struct address_space *mapping = page->mapping; struct inode *inode = mapping->host; @@ -1971,13 +1972,30 @@ static int fuse_writepage_locked(struct page *page, struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_req *req; struct page *tmp_page; + struct fuse_file *ff; + int err = 0; if (fuse_page_is_writeback(inode, page->index)) { if (wbc->sync_mode != WB_SYNC_ALL) { redirty_page_for_writepage(wbc, 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) + goto dummy_end_page_wb_err; + + /* 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)) { + if (ff_pp) + *ff_pp = ff; + goto dummy_end_page_wb; + } + + fuse_release_ff(inode, ff); } if (test_set_page_writeback(page)) @@ -1995,6 +2013,8 @@ static int fuse_writepage_locked(struct page *page, req->ff = fuse_write_file(fc, fi); if (!req->ff) goto err_nofile; + if (ff_pp) + *ff_pp = fuse_file_get(req->ff); fuse_write_fill(req, req->ff, page_offset(page), 0); fuse_account_request(fc, PAGE_CACHE_SIZE); @@ -2029,13 +2049,23 @@ err_free: err: end_page_writeback(page); return -ENOMEM; + +dummy_end_page_wb_err: + printk("FUSE: page under fwb dirtied on dead file\n"); + err = -EIO; + /* fall through ... */ +dummy_end_page_wb: + if (test_set_page_writeback(page)) + BUG(); + end_page_writeback(page); + return err; } static int fuse_writepage(struct page *page, struct writeback_control *wbc) { int err; - err = fuse_writepage_locked(page, wbc); + err = fuse_writepage_locked(page, wbc, NULL); unlock_page(page); return err; @@ -2423,9 +2453,18 @@ static int fuse_launder_page(struct page *page) struct writeback_control wbc = { .sync_mode = WB_SYNC_ALL, }; - err = fuse_writepage_locked(page, &wbc); - if (!err) - fuse_wait_on_page_writeback(inode, page->index); + struct fuse_file *ff = NULL; + err = fuse_writepage_locked(page, &wbc, &ff); + if (!err) { + /* + * We need to check FAIL_IMMEDIATELY because otherwise + * fuse_do_setattr may stick in invalidate_inode_pages2 + * forever (if fuse_invalidate_files is in progress). + */ + __fuse_wait_on_page_writeback_or_invalidate(inode, + ff, page->index); + fuse_release_ff(inode, ff); + } } return err; } _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel