linux-btrfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5] Proper error handling for the compressed write path
@ 2014-10-06 21:14 Filipe Manana
  2014-10-06 21:14 ` [PATCH 1/5] Btrfs: set page and mapping error on compressed write failure Filipe Manana
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Filipe Manana @ 2014-10-06 21:14 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Filipe Manana

This patchset fixes several issues in inode.c:submit_compressed_extents()
when one of the functions it calls fails. These issues range from hangs,
missing error reporting (silent failure), memory leaks and pages not getting
released.

Filipe Manana (5):
  Btrfs: set page and mapping error on compressed write failure
  Btrfs: fix hang on compressed write error
  Btrfs: don't leak pages and memory on compressed write error
  Btrfs: process all async extents on compressed write failure
  Btrfs: make inode.c:submit_compressed_extents() return void

 fs/btrfs/extent_io.c |  5 +++++
 fs/btrfs/extent_io.h |  1 +
 fs/btrfs/inode.c     | 56 ++++++++++++++++++++++++++++++++++------------------
 3 files changed, 43 insertions(+), 19 deletions(-)

-- 
1.9.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/5] Btrfs: set page and mapping error on compressed write failure
  2014-10-06 21:14 [PATCH 0/5] Proper error handling for the compressed write path Filipe Manana
@ 2014-10-06 21:14 ` Filipe Manana
  2014-10-06 21:14 ` [PATCH 2/5] Btrfs: fix hang on compressed write error Filipe Manana
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Filipe Manana @ 2014-10-06 21:14 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Filipe Manana

If we fail in submit_compressed_extents() before calling btrfs_submit_compressed_write(),
we start and end the writeback for the pages (clear their dirty flag, unlock them, etc)
but we don't tag the pages, nor the inode's mapping, with an error. This makes it
impossible for a caller of filemap_fdatawait_range() (fsync, or transaction commit
for e.g.) know that there was an error.

Note that the return value of submit_compressed_extents() is useless, as that function
is executed by a workqueue task and not directly by the fill_delalloc callback. This
means the writepage/s callbacks of the inode's address space operations don't get that
return value.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/extent_io.c | 5 +++++
 fs/btrfs/extent_io.h | 1 +
 fs/btrfs/inode.c     | 3 ++-
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 9cc757f..865594c 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -1746,6 +1746,9 @@ int extent_clear_unlock_delalloc(struct inode *inode, u64 start, u64 end,
 	if (page_ops == 0)
 		return 0;
 
+	if ((page_ops & PAGE_SET_ERROR) && nr_pages > 0)
+		mapping_set_error(inode->i_mapping, -EIO);
+
 	while (nr_pages > 0) {
 		ret = find_get_pages_contig(inode->i_mapping, index,
 				     min_t(unsigned long,
@@ -1763,6 +1766,8 @@ int extent_clear_unlock_delalloc(struct inode *inode, u64 start, u64 end,
 				clear_page_dirty_for_io(pages[i]);
 			if (page_ops & PAGE_SET_WRITEBACK)
 				set_page_writeback(pages[i]);
+			if (page_ops & PAGE_SET_ERROR)
+				SetPageError(pages[i]);
 			if (page_ops & PAGE_END_WRITEBACK)
 				end_page_writeback(pages[i]);
 			if (page_ops & PAGE_UNLOCK)
diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
index 06f030c..5654e14 100644
--- a/fs/btrfs/extent_io.h
+++ b/fs/btrfs/extent_io.h
@@ -52,6 +52,7 @@
 #define PAGE_SET_WRITEBACK	(1 << 2)
 #define PAGE_END_WRITEBACK	(1 << 3)
 #define PAGE_SET_PRIVATE2	(1 << 4)
+#define PAGE_SET_ERROR		(1 << 5)
 
 /*
  * page->private values.  Every page that is controlled by the extent
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 344a322..cefa618 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -832,7 +832,8 @@ out_free:
 				     NULL, EXTENT_LOCKED | EXTENT_DELALLOC |
 				     EXTENT_DEFRAG | EXTENT_DO_ACCOUNTING,
 				     PAGE_UNLOCK | PAGE_CLEAR_DIRTY |
-				     PAGE_SET_WRITEBACK | PAGE_END_WRITEBACK);
+				     PAGE_SET_WRITEBACK | PAGE_END_WRITEBACK |
+				     PAGE_SET_ERROR);
 	kfree(async_extent);
 	goto again;
 }
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/5] Btrfs: fix hang on compressed write error
  2014-10-06 21:14 [PATCH 0/5] Proper error handling for the compressed write path Filipe Manana
  2014-10-06 21:14 ` [PATCH 1/5] Btrfs: set page and mapping error on compressed write failure Filipe Manana
@ 2014-10-06 21:14 ` Filipe Manana
  2014-10-06 21:14 ` [PATCH 3/5] Btrfs: don't leak pages and memory " Filipe Manana
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Filipe Manana @ 2014-10-06 21:14 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Filipe Manana

In inode.c:submit_compressed_extents(), before calling btrfs_submit_compressed_write()
we start writeback for all pages, clear their dirty flag, unlock them, etc, but if
btrfs_submit_compressed_write() fails (at the moment it can only fail with -ENOMEM),
we never end the writeback on the pages, so any filemap_fdatawait_range() call will
hang forever. We were also not calling the writepage end io hook, which means the
corresponding ordered extent will never complete and all its waiters will block
forever, such as a full fsync (via btrfs_wait_ordered_range()).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/inode.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index cefa618..e2c4650 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -814,6 +814,20 @@ retry:
 				    ins.objectid,
 				    ins.offset, async_extent->pages,
 				    async_extent->nr_pages);
+		if (ret) {
+			struct extent_io_tree *tree = &BTRFS_I(inode)->io_tree;
+			struct page *p = async_extent->pages[0];
+			const u64 start = async_extent->start;
+			const u64 end = start + async_extent->ram_size - 1;
+
+			p->mapping = inode->i_mapping;
+			tree->ops->writepage_end_io_hook(p, start, end,
+							 NULL, 0);
+			p->mapping = NULL;
+			extent_clear_unlock_delalloc(inode, start, end, NULL, 0,
+						     PAGE_END_WRITEBACK |
+						     PAGE_SET_ERROR);
+		}
 		alloc_hint = ins.objectid + ins.offset;
 		kfree(async_extent);
 		if (ret)
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/5] Btrfs: don't leak pages and memory on compressed write error
  2014-10-06 21:14 [PATCH 0/5] Proper error handling for the compressed write path Filipe Manana
  2014-10-06 21:14 ` [PATCH 1/5] Btrfs: set page and mapping error on compressed write failure Filipe Manana
  2014-10-06 21:14 ` [PATCH 2/5] Btrfs: fix hang on compressed write error Filipe Manana
@ 2014-10-06 21:14 ` Filipe Manana
  2014-10-06 21:14 ` [PATCH 4/5] Btrfs: process all async extents on compressed write failure Filipe Manana
  2014-10-06 21:14 ` [PATCH 5/5] Btrfs: make inode.c:submit_compressed_extents() return void Filipe Manana
  4 siblings, 0 replies; 6+ messages in thread
From: Filipe Manana @ 2014-10-06 21:14 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Filipe Manana

In inode.c:submit_compressed_extents(), if we fail before calling
btrfs_submit_compressed_write(), or when that function fails, we
were freeing the async_extent structure without releasing its pages
and freeing the pages array.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/inode.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index e2c4650..a3e2330 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -633,6 +633,22 @@ free_pages_out:
 	goto out;
 }
 
+static void free_async_extent_pages(struct async_extent *async_extent)
+{
+	int i;
+
+	if (!async_extent->pages)
+		return;
+
+	for (i = 0; i < async_extent->nr_pages; i++) {
+		WARN_ON(async_extent->pages[i]->mapping);
+		page_cache_release(async_extent->pages[i]);
+	}
+	kfree(async_extent->pages);
+	async_extent->nr_pages = 0;
+	async_extent->pages = NULL;
+}
+
 /*
  * phase two of compressed writeback.  This is the ordered portion
  * of the code, which only gets called in the order the work was
@@ -709,15 +725,7 @@ retry:
 					   async_extent->compressed_size,
 					   0, alloc_hint, &ins, 1, 1);
 		if (ret) {
-			int i;
-
-			for (i = 0; i < async_extent->nr_pages; i++) {
-				WARN_ON(async_extent->pages[i]->mapping);
-				page_cache_release(async_extent->pages[i]);
-			}
-			kfree(async_extent->pages);
-			async_extent->nr_pages = 0;
-			async_extent->pages = NULL;
+			free_async_extent_pages(async_extent);
 
 			if (ret == -ENOSPC) {
 				unlock_extent(io_tree, async_extent->start,
@@ -827,6 +835,7 @@ retry:
 			extent_clear_unlock_delalloc(inode, start, end, NULL, 0,
 						     PAGE_END_WRITEBACK |
 						     PAGE_SET_ERROR);
+			free_async_extent_pages(async_extent);
 		}
 		alloc_hint = ins.objectid + ins.offset;
 		kfree(async_extent);
@@ -848,6 +857,7 @@ out_free:
 				     PAGE_UNLOCK | PAGE_CLEAR_DIRTY |
 				     PAGE_SET_WRITEBACK | PAGE_END_WRITEBACK |
 				     PAGE_SET_ERROR);
+	free_async_extent_pages(async_extent);
 	kfree(async_extent);
 	goto again;
 }
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/5] Btrfs: process all async extents on compressed write failure
  2014-10-06 21:14 [PATCH 0/5] Proper error handling for the compressed write path Filipe Manana
                   ` (2 preceding siblings ...)
  2014-10-06 21:14 ` [PATCH 3/5] Btrfs: don't leak pages and memory " Filipe Manana
@ 2014-10-06 21:14 ` Filipe Manana
  2014-10-06 21:14 ` [PATCH 5/5] Btrfs: make inode.c:submit_compressed_extents() return void Filipe Manana
  4 siblings, 0 replies; 6+ messages in thread
From: Filipe Manana @ 2014-10-06 21:14 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Filipe Manana

If we had an error when processing one of the async extents from our list,
we were not processing the remaining async extents, meaning we would leak
those async_extent structs, never release the pages with the compressed
data and never unlock and clear the dirty flag from the inode's pages (those
that correspond to the uncompressed content).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/inode.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index a3e2330..8636499 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -839,13 +839,9 @@ retry:
 		}
 		alloc_hint = ins.objectid + ins.offset;
 		kfree(async_extent);
-		if (ret)
-			goto out;
 		cond_resched();
 	}
-	ret = 0;
-out:
-	return ret;
+	return 0;
 out_free_reserve:
 	btrfs_free_reserved_extent(root, ins.objectid, ins.offset, 1);
 out_free:
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 5/5] Btrfs: make inode.c:submit_compressed_extents() return void
  2014-10-06 21:14 [PATCH 0/5] Proper error handling for the compressed write path Filipe Manana
                   ` (3 preceding siblings ...)
  2014-10-06 21:14 ` [PATCH 4/5] Btrfs: process all async extents on compressed write failure Filipe Manana
@ 2014-10-06 21:14 ` Filipe Manana
  4 siblings, 0 replies; 6+ messages in thread
From: Filipe Manana @ 2014-10-06 21:14 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Filipe Manana

Its return value is completely ignored by its single caller and it's
useless anyway, since errors are indicated through SetPageError and
the bit AS_EIO set in the flags of the inode's mapping. The caller
can't do anything with the value, as it's invoked from a workqueue
task and not by the task calling filemap_fdatawrite_range (which calls
the writepages address space callback, which in turn calls the inode's
fill_delalloc callback).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/inode.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8636499..7635b1d 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -655,7 +655,7 @@ static void free_async_extent_pages(struct async_extent *async_extent)
  * queued.  We walk all the async extents created by compress_file_range
  * and send them down to the disk.
  */
-static noinline int submit_compressed_extents(struct inode *inode,
+static noinline void submit_compressed_extents(struct inode *inode,
 					      struct async_cow *async_cow)
 {
 	struct async_extent *async_extent;
@@ -667,9 +667,6 @@ static noinline int submit_compressed_extents(struct inode *inode,
 	struct extent_io_tree *io_tree;
 	int ret = 0;
 
-	if (list_empty(&async_cow->extents))
-		return 0;
-
 again:
 	while (!list_empty(&async_cow->extents)) {
 		async_extent = list_entry(async_cow->extents.next,
@@ -841,7 +838,7 @@ retry:
 		kfree(async_extent);
 		cond_resched();
 	}
-	return 0;
+	return;
 out_free_reserve:
 	btrfs_free_reserved_extent(root, ins.objectid, ins.offset, 1);
 out_free:
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2014-10-06 20:14 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-10-06 21:14 [PATCH 0/5] Proper error handling for the compressed write path Filipe Manana
2014-10-06 21:14 ` [PATCH 1/5] Btrfs: set page and mapping error on compressed write failure Filipe Manana
2014-10-06 21:14 ` [PATCH 2/5] Btrfs: fix hang on compressed write error Filipe Manana
2014-10-06 21:14 ` [PATCH 3/5] Btrfs: don't leak pages and memory " Filipe Manana
2014-10-06 21:14 ` [PATCH 4/5] Btrfs: process all async extents on compressed write failure Filipe Manana
2014-10-06 21:14 ` [PATCH 5/5] Btrfs: make inode.c:submit_compressed_extents() return void Filipe Manana

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).