btrfs: don't fail writeback when allocating the compression context fails
authorChristoph Hellwig <hch@lst.de>
Wed, 31 May 2023 06:04:54 +0000 (08:04 +0200)
committerDavid Sterba <dsterba@suse.com>
Mon, 19 Jun 2023 11:59:35 +0000 (13:59 +0200)
If cow_file_range_async fails to allocate the asynchronous writeback
context, it currently returns an error and entirely fails the writeback.
This is not a good idea as a writeback failure is a non-temporary error
condition that will make the file system unusable.  Just fall back to
synchronous uncompressed writeback instead.  This requires us to delay
setting the BTRFS_INODE_HAS_ASYNC_EXTENT flag until we've committed to
the async writeback.

The compression checks INODE_NOCOMPRESS and FORCE_COMPRESS are moved
from cow_file_range_async to the preceding checks btrfs_run_delalloc_range().

Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/inode.c

index b8c6488..2c7d726 100644 (file)
@@ -1720,58 +1720,36 @@ static noinline void async_cow_free(struct btrfs_work *work)
                kvfree(async_cow);
 }
 
-static int cow_file_range_async(struct btrfs_inode *inode,
-                               struct writeback_control *wbc,
-                               struct page *locked_page,
-                               u64 start, u64 end, int *page_started,
-                               unsigned long *nr_written)
+static bool cow_file_range_async(struct btrfs_inode *inode,
+                                struct writeback_control *wbc,
+                                struct page *locked_page,
+                                u64 start, u64 end, int *page_started,
+                                unsigned long *nr_written)
 {
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
        struct cgroup_subsys_state *blkcg_css = wbc_blkcg_css(wbc);
        struct async_cow *ctx;
        struct async_chunk *async_chunk;
        unsigned long nr_pages;
-       u64 cur_end;
        u64 num_chunks = DIV_ROUND_UP(end - start, SZ_512K);
        int i;
-       bool should_compress;
        unsigned nofs_flag;
        const blk_opf_t write_flags = wbc_to_write_flags(wbc);
 
-       unlock_extent(&inode->io_tree, start, end, NULL);
-
-       if (inode->flags & BTRFS_INODE_NOCOMPRESS &&
-           !btrfs_test_opt(fs_info, FORCE_COMPRESS)) {
-               num_chunks = 1;
-               should_compress = false;
-       } else {
-               should_compress = true;
-       }
-
        nofs_flag = memalloc_nofs_save();
        ctx = kvmalloc(struct_size(ctx, chunks, num_chunks), GFP_KERNEL);
        memalloc_nofs_restore(nofs_flag);
+       if (!ctx)
+               return false;
 
-       if (!ctx) {
-               unsigned clear_bits = EXTENT_LOCKED | EXTENT_DELALLOC |
-                       EXTENT_DELALLOC_NEW | EXTENT_DEFRAG |
-                       EXTENT_DO_ACCOUNTING;
-               unsigned long page_ops = PAGE_UNLOCK | PAGE_START_WRITEBACK |
-                                        PAGE_END_WRITEBACK | PAGE_SET_ERROR;
-
-               extent_clear_unlock_delalloc(inode, start, end, locked_page,
-                                            clear_bits, page_ops);
-               return -ENOMEM;
-       }
+       unlock_extent(&inode->io_tree, start, end, NULL);
+       set_bit(BTRFS_INODE_HAS_ASYNC_EXTENT, &inode->runtime_flags);
 
        async_chunk = ctx->chunks;
        atomic_set(&ctx->num_chunks, num_chunks);
 
        for (i = 0; i < num_chunks; i++) {
-               if (should_compress)
-                       cur_end = min(end, start + SZ_512K - 1);
-               else
-                       cur_end = end;
+               u64 cur_end = min(end, start + SZ_512K - 1);
 
                /*
                 * igrab is called higher up in the call chain, take only the
@@ -1832,7 +1810,7 @@ static int cow_file_range_async(struct btrfs_inode *inode,
                start = cur_end + 1;
        }
        *page_started = 1;
-       return 0;
+       return true;
 }
 
 static noinline int run_delalloc_zoned(struct btrfs_inode *inode,
@@ -2413,7 +2391,7 @@ int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct page *locked_page
                u64 start, u64 end, int *page_started, unsigned long *nr_written,
                struct writeback_control *wbc)
 {
-       int ret;
+       int ret = 0;
        const bool zoned = btrfs_is_zoned(inode->root->fs_info);
 
        /*
@@ -2434,19 +2412,23 @@ int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct page *locked_page
                ASSERT(!zoned || btrfs_is_data_reloc_root(inode->root));
                ret = run_delalloc_nocow(inode, locked_page, start, end,
                                         page_started, nr_written);
-       } else if (!btrfs_inode_can_compress(inode) ||
-                  !inode_need_compress(inode, start, end)) {
-               if (zoned)
-                       ret = run_delalloc_zoned(inode, locked_page, start, end,
-                                                page_started, nr_written);
-               else
-                       ret = cow_file_range(inode, locked_page, start, end,
-                                            page_started, nr_written, 1, NULL);
-       } else {
-               set_bit(BTRFS_INODE_HAS_ASYNC_EXTENT, &inode->runtime_flags);
-               ret = cow_file_range_async(inode, wbc, locked_page, start, end,
-                                          page_started, nr_written);
+               goto out;
        }
+
+       if (btrfs_inode_can_compress(inode) &&
+           inode_need_compress(inode, start, end) &&
+           cow_file_range_async(inode, wbc, locked_page, start,
+                                end, page_started, nr_written))
+               goto out;
+
+       if (zoned)
+               ret = run_delalloc_zoned(inode, locked_page, start, end,
+                                        page_started, nr_written);
+       else
+               ret = cow_file_range(inode, locked_page, start, end,
+                                    page_started, nr_written, 1, NULL);
+
+out:
        ASSERT(ret <= 0);
        if (ret)
                btrfs_cleanup_ordered_extents(inode, locked_page, start,