Linux Btrfs filesystem development
 help / color / mirror / Atom feed
From: Qu Wenruo <wqu@suse.com>
To: Daniel Vacek <neelx@suse.com>, Qu Wenruo <quwenruo.btrfs@gmx.com>
Cc: linux-btrfs@vger.kernel.org
Subject: Re: [PATCH] btrfs: move async csum generation out of experimental features
Date: Sat, 5 Sep 2026 10:27:47 +0930	[thread overview]
Message-ID: <130fdc9a-2d51-49cd-bf7f-c8e4b8fa19aa@suse.com> (raw)
In-Reply-To: <CAPjX3FdkWegsttPk9FiTiVUp9dcdycWWmecnpSxcgrpwnw=TEg@mail.gmail.com>



在 2026/9/3 22:14, Daniel Vacek 写道:
> On Thu, 3 Sept 2026 at 13:27, Qu Wenruo <quwenruo.btrfs@gmx.com> wrote:
>> 在 2026/9/3 18:50, Daniel Vacek 写道:
>>> On Thu, 3 Sept 2026 at 10:40, Qu Wenruo <wqu@suse.com> wrote:
>>>> Commit dd57c78aec39 ("btrfs: introduce btrfs_bio::async_csum")
>>>> introduced asynchronous data checksum generation for data writes.
>>>>
>>>> That feature can improve write performance, and was introduced in v6.19.
>>>> We have not experienced bugs related to that, so it's time to move it out
>>>> of experimental features for end users.
>>>>
>>>> Furthermore since the async checksum generation means
>>>> should_async_write() will always return false, we no longer need to
>>>> maintain the fs_info->workers workqueue, as the async checksum
>>>> generation is using the system_percpu_wq.
>>>
>>> I understand these are two related changes in one patch here. If
>>> removing async writes depends on async checksums, it would be nice to
>>> split this into a patch series.
>>>
>>> Though I guess can live with that. Looks good enough to me.
>>>
>>> Reviewed-by: Daniel Vacek <neelx@suse.com>
>>
>> Thanks a lot for the review.
>>
>>
>> I just want to mention that, Sashiko mentioned a problem that I believe
>> is valid, but not sure how realistic it will be in the real world.
>>
>>
>> Sashiko mentioned that, the old worker workqueue has WQ_MEM_RECLAIM
>> flag, thus there will always be a rescuer to ensure forward progress.
>> (Although for x86_64 and other common archs or EXPERIMENTAL builds, we
>> never utilize that worker anyway)
>>
>> But the new schedule_work() is using system_percpu_wq, which doesn't has
>> that flag.
> 
> Well, I think squashing this fixup to my patch is the right thing to do anyways:
> 
> diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
> index 0fed4e0d32d5..984d87ad79b0 100644
> --- a/fs/btrfs/file-item.c
> +++ b/fs/btrfs/file-item.c
> @@ -855,7 +855,7 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
>       }
>       bio_inc_remaining(bio);
>       INIT_WORK(&bbio->csum_work, csum_one_bio_work);
> -    schedule_work(&bbio->csum_work);
> +    queue_work(fs_info->endio_workers, &bbio->csum_work);
>       return 0;
>   }

This has one new problem though.

The csum_one_bio_work() can queue a new work into the same endio_workers.

It happens like this:

csum_one_bio_work()
|- bio_endio()
|  |- btrfs_simple_end_io()
       |- queue_work() into endio_workers again

This can cause problems with flush_workqueue() in close_ctree(), where 
we only flush the workqueue once, which doesn't ensure newly created 
work is also flushed.

We can use drain_workqueue() instead to fix it easily though.

Mind to send out a proper patch for it?

Thanks,
Qu

> 
> 
> That should make sashiko happy as well as btrfs end_io work running on
> btrfs end_io work queues.
> 
> And we should stay on the safe side wrt memory reclaim.
> 
> --nX
> 
> 
>> And since the csum generation is needed for data writeback, if under
>> very heavy memory pressure, there may be no worker to ensure the csum
>> work can be queued, in that case, it will hang the writeback (which is
>> triggered to reclaim memory).
>>
>> If we really want to address that problem, your idea about splitting the
>> patch will make a lot of sense.
>> We will need to keep the old infrastructure first, convert the worker to
>> a regular workqueue, and call queue_work(), then fully remove unused code.
>>
>>
>> But on the other hand, I doubt how pratically it is for anyone to stall
>> system_percpu_wq.
>> It's very common utilized across the whole kernel, and if it really
>> stalls I think there are a lot of more things to bother before btrfs.
>>
>> Another thing is, if we use fs_info::workers, we will follow the
>> existing wq flags, which means no PERCPU flag.
>> And according to the existing docs for those flags, PERCPU wq has a
>> better performance due to CPU locality.
>>
>>
>> So overall I think the current version is good enough, but not perfect.
>> On the other hand, using a WQ_MEM_RECLAIM seems more "correct", but will
>> definitely introduce some extra pentalty.
>>
>> If someone else has some idea on this Sashiko review, or very familiar
>> with the WQ_MEM_RECLAIM situation, any comment will be appreciated.
>>
>> Thanks,
>> Qu
>>
>>>
>>>> Suggested-by: Daniel Vacek <neelx@suse.com>
>>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>>>> ---
>>>>    fs/btrfs/bio.c       | 136 +------------------------------------------
>>>>    fs/btrfs/disk-io.c   |  20 +------
>>>>    fs/btrfs/file-item.c |   6 +-
>>>>    fs/btrfs/file-item.h |   2 +-
>>>>    fs/btrfs/fs.h        |  10 ----
>>>>    fs/btrfs/super.c     |   1 -
>>>>    6 files changed, 4 insertions(+), 171 deletions(-)
>>>>
>>>> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
>>>> index 771b7d598aee..e08006543c3d 100644
>>>> --- a/fs/btrfs/bio.c
>>>> +++ b/fs/btrfs/bio.c
>>>> @@ -569,136 +569,7 @@ static int btrfs_bio_csum(struct btrfs_bio *bbio)
>>>>    {
>>>>           if (bbio->bio.bi_opf & REQ_META)
>>>>                   return btree_csum_one_bio(bbio);
>>>> -#ifdef CONFIG_BTRFS_EXPERIMENTAL
>>>> -       return btrfs_csum_one_bio(bbio, true);
>>>> -#else
>>>> -       return btrfs_csum_one_bio(bbio, false);
>>>> -#endif
>>>> -}
>>>> -
>>>> -/*
>>>> - * Async submit bios are used to offload expensive checksumming onto the worker
>>>> - * threads.
>>>> - */
>>>> -struct async_submit_bio {
>>>> -       struct btrfs_bio *bbio;
>>>> -       struct btrfs_io_context *bioc;
>>>> -       struct btrfs_io_stripe smap;
>>>> -       int mirror_num;
>>>> -       struct btrfs_work work;
>>>> -};
>>>> -
>>>> -/*
>>>> - * In order to insert checksums into the metadata in large chunks, we wait
>>>> - * until bio submission time.   All the pages in the bio are checksummed and
>>>> - * sums are attached onto the ordered extent record.
>>>> - *
>>>> - * At IO completion time the csums attached on the ordered extent record are
>>>> - * inserted into the btree.
>>>> - */
>>>> -static void run_one_async_start(struct btrfs_work *work)
>>>> -{
>>>> -       struct async_submit_bio *async =
>>>> -               container_of(work, struct async_submit_bio, work);
>>>> -       int ret;
>>>> -
>>>> -       ret = btrfs_bio_csum(async->bbio);
>>>> -       if (ret)
>>>> -               async->bbio->bio.bi_status = errno_to_blk_status(ret);
>>>> -}
>>>> -
>>>> -/*
>>>> - * In order to insert checksums into the metadata in large chunks, we wait
>>>> - * until bio submission time.   All the pages in the bio are checksummed and
>>>> - * sums are attached onto the ordered extent record.
>>>> - *
>>>> - * At IO completion time the csums attached on the ordered extent record are
>>>> - * inserted into the tree.
>>>> - *
>>>> - * If called with @do_free == true, then it will free the work struct.
>>>> - */
>>>> -static void run_one_async_done(struct btrfs_work *work, bool do_free)
>>>> -{
>>>> -       struct async_submit_bio *async =
>>>> -               container_of(work, struct async_submit_bio, work);
>>>> -       struct bio *bio = &async->bbio->bio;
>>>> -
>>>> -       if (do_free) {
>>>> -               kfree(container_of(work, struct async_submit_bio, work));
>>>> -               return;
>>>> -       }
>>>> -
>>>> -       /* If an error occurred we just want to clean up the bio and move on. */
>>>> -       if (bio->bi_status) {
>>>> -               btrfs_bio_end_io(async->bbio, bio->bi_status);
>>>> -               return;
>>>> -       }
>>>> -
>>>> -       /*
>>>> -        * All of the bios that pass through here are from async helpers.
>>>> -        * Use REQ_BTRFS_CGROUP_PUNT to issue them from the owning cgroup's
>>>> -        * context.  This changes nothing when cgroups aren't in use.
>>>> -        */
>>>> -       bio->bi_opf |= REQ_BTRFS_CGROUP_PUNT;
>>>> -       btrfs_submit_bio(bio, async->bioc, &async->smap, async->mirror_num);
>>>> -}
>>>> -
>>>> -static bool should_async_write(struct btrfs_bio *bbio)
>>>> -{
>>>> -       struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info;
>>>> -       bool auto_csum_mode = true;
>>>> -
>>>> -#ifdef CONFIG_BTRFS_EXPERIMENTAL
>>>> -       /*
>>>> -        * Write bios will calculate checksum and submit bio at the same time.
>>>> -        * Unless explicitly required don't offload serial csum calculate and bio
>>>> -        * submit into a workqueue.
>>>> -        */
>>>> -       return false;
>>>> -#endif
>>>> -
>>>> -       /* Submit synchronously if the checksum implementation is fast. */
>>>> -       if (auto_csum_mode && test_bit(BTRFS_FS_CSUM_IMPL_FAST, &fs_info->flags))
>>>> -               return false;
>>>> -
>>>> -       /*
>>>> -        * Try to defer the submission to a workqueue to parallelize the
>>>> -        * checksum calculation unless the I/O is issued synchronously.
>>>> -        */
>>>> -       if (op_is_sync(bbio->bio.bi_opf))
>>>> -               return false;
>>>> -
>>>> -       /* Zoned devices require I/O to be submitted in order. */
>>>> -       if ((bbio->bio.bi_opf & REQ_META) && btrfs_is_zoned(fs_info))
>>>> -               return false;
>>>> -
>>>> -       return true;
>>>> -}
>>>> -
>>>> -/*
>>>> - * Submit bio to an async queue.
>>>> - *
>>>> - * Return true if the work has been successfully submitted, else false.
>>>> - */
>>>> -static bool btrfs_wq_submit_bio(struct btrfs_bio *bbio,
>>>> -                               struct btrfs_io_context *bioc,
>>>> -                               struct btrfs_io_stripe *smap, int mirror_num)
>>>> -{
>>>> -       struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info;
>>>> -       struct async_submit_bio *async;
>>>> -
>>>> -       async = kmalloc_obj(*async, GFP_NOFS);
>>>> -       if (!async)
>>>> -               return false;
>>>> -
>>>> -       async->bbio = bbio;
>>>> -       async->bioc = bioc;
>>>> -       async->smap = *smap;
>>>> -       async->mirror_num = mirror_num;
>>>> -
>>>> -       btrfs_init_work(&async->work, run_one_async_start, run_one_async_done);
>>>> -       btrfs_queue_work(fs_info->workers, &async->work);
>>>> -       return true;
>>>> +       return btrfs_csum_one_bio(bbio);
>>>>    }
>>>>
>>>>    static u64 btrfs_append_map_length(struct btrfs_bio *bbio, u64 map_length)
>>>> @@ -806,10 +677,6 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
>>>>                   if (!(inode->flags & BTRFS_INODE_NODATASUM) &&
>>>>                       !test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state) &&
>>>>                       !btrfs_is_data_reloc_root(inode->root) && !bbio->is_remap) {
>>>> -                       if (should_async_write(bbio) &&
>>>> -                           btrfs_wq_submit_bio(bbio, bioc, &smap, mirror_num))
>>>> -                               goto done;
>>>> -
>>>>                           ret = btrfs_bio_csum(bbio);
>>>>                           status = errno_to_blk_status(ret);
>>>>                           if (status)
>>>> @@ -824,7 +691,6 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
>>>>           }
>>>>
>>>>           btrfs_submit_bio(bio, bioc, &smap, mirror_num);
>>>> -done:
>>>>           return map_length == length;
>>>>
>>>>    fail:
>>>> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
>>>> index a1d83ad9a4c0..0046f3cb75a1 100644
>>>> --- a/fs/btrfs/disk-io.c
>>>> +++ b/fs/btrfs/disk-io.c
>>>> @@ -1776,7 +1776,6 @@ static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info)
>>>>           if (fs_info->fixup_workers)
>>>>                   destroy_workqueue(fs_info->fixup_workers);
>>>>           btrfs_destroy_workqueue(fs_info->delalloc_workers);
>>>> -       btrfs_destroy_workqueue(fs_info->workers);
>>>>           if (fs_info->endio_workers)
>>>>                   destroy_workqueue(fs_info->endio_workers);
>>>>           if (fs_info->rmw_workers)
>>>> @@ -1968,9 +1967,6 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info)
>>>>           unsigned int flags = WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_UNBOUND;
>>>>           unsigned int ordered_flags = WQ_MEM_RECLAIM | WQ_FREEZABLE;
>>>>
>>>> -       fs_info->workers =
>>>> -               btrfs_alloc_workqueue(fs_info, "worker", flags, max_active, 16);
>>>> -
>>>>           fs_info->delalloc_workers =
>>>>                   btrfs_alloc_workqueue(fs_info, "delalloc",
>>>>                                         flags, max_active, 2);
>>>> @@ -2005,8 +2001,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info)
>>>>           fs_info->discard_ctl.discard_workers =
>>>>                   alloc_ordered_workqueue("btrfs-discard", WQ_FREEZABLE);
>>>>
>>>> -       if (!(fs_info->workers &&
>>>> -             fs_info->delalloc_workers && fs_info->flush_workers &&
>>>> +       if (!(fs_info->delalloc_workers && fs_info->flush_workers &&
>>>>                 fs_info->endio_workers && fs_info->endio_meta_workers &&
>>>>                 fs_info->endio_write_workers &&
>>>>                 fs_info->endio_freespace_worker && fs_info->rmw_workers &&
>>>> @@ -4437,19 +4432,6 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info)
>>>>            */
>>>>           btrfs_flush_workqueue(fs_info->delalloc_workers);
>>>>
>>>> -       /*
>>>> -        * We can have ordered extents getting their last reference dropped from
>>>> -        * the fs_info->workers queue because for async writes for data bios we
>>>> -        * queue a work for that queue, at btrfs_wq_submit_bio(), that runs
>>>> -        * run_one_async_done() which calls btrfs_bio_end_io() in case the bio
>>>> -        * has an error, and that later function can do the final
>>>> -        * btrfs_put_ordered_extent() on the ordered extent attached to the bio,
>>>> -        * which adds a delayed iput for the inode. So we must flush the queue
>>>> -        * so that we don't have delayed iputs after committing the current
>>>> -        * transaction below and stopping the cleaner and transaction kthreads.
>>>> -        */
>>>> -       btrfs_flush_workqueue(fs_info->workers);
>>>> -
>>>>           /*
>>>>            * When finishing a compressed write bio we schedule a work queue item
>>>>            * to finish an ordered extent - end_bbio_compressed_write()
>>>> diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
>>>> index 0fed4e0d32d5..7dbd9b8eec11 100644
>>>> --- a/fs/btrfs/file-item.c
>>>> +++ b/fs/btrfs/file-item.c
>>>> @@ -825,7 +825,7 @@ static void csum_one_bio_work(struct work_struct *work)
>>>>    /*
>>>>     * Calculate checksums of the data contained inside a bio.
>>>>     */
>>>> -int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
>>>> +int btrfs_csum_one_bio(struct btrfs_bio *bbio)
>>>>    {
>>>>           struct btrfs_ordered_extent *ordered = bbio->ordered;
>>>>           struct btrfs_inode *inode = bbio->inode;
>>>> @@ -849,10 +849,6 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
>>>>           btrfs_add_ordered_sum(ordered, sums);
>>>>
>>>>           bbio->csum_saved_iter = bio->bi_iter;
>>>> -       if (!async) {
>>>> -               csum_one_bio(bbio);
>>>> -               return 0;
>>>> -       }
>>>>           bio_inc_remaining(bio);
>>>>           INIT_WORK(&bbio->csum_work, csum_one_bio_work);
>>>>           schedule_work(&bbio->csum_work);
>>>> diff --git a/fs/btrfs/file-item.h b/fs/btrfs/file-item.h
>>>> index 6c678787c770..60a0eb17b3f6 100644
>>>> --- a/fs/btrfs/file-item.h
>>>> +++ b/fs/btrfs/file-item.h
>>>> @@ -64,7 +64,7 @@ int btrfs_lookup_file_extent(struct btrfs_trans_handle *trans,
>>>>    int btrfs_insert_data_csums(struct btrfs_trans_handle *trans,
>>>>                               struct btrfs_root *root,
>>>>                               struct btrfs_ordered_sum *sums);
>>>> -int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async);
>>>> +int btrfs_csum_one_bio(struct btrfs_bio *bbio);
>>>>    int btrfs_alloc_dummy_sum(struct btrfs_bio *bbio);
>>>>    int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end,
>>>>                                struct list_head *list, int search_commit,
>>>> diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
>>>> index 3eba8438593c..203f7131f737 100644
>>>> --- a/fs/btrfs/fs.h
>>>> +++ b/fs/btrfs/fs.h
>>>> @@ -696,16 +696,6 @@ struct btrfs_fs_info {
>>>>           /* All fs/file tree roots that have delalloc inodes. */
>>>>           struct list_head delalloc_roots;
>>>>
>>>> -       /*
>>>> -        * There is a pool of worker threads for checksumming during writes and
>>>> -        * a pool for checksumming after reads.  This is because readers can
>>>> -        * run with FS locks held, and the writers may be waiting for those
>>>> -        * locks.  We don't want ordering in the pending list to cause
>>>> -        * deadlocks, and so the two are serviced separately.
>>>> -        *
>>>> -        * A third pool does submit_bio to avoid deadlocking with the other two.
>>>> -        */
>>>> -       struct btrfs_workqueue *workers;
>>>>           struct btrfs_workqueue *delalloc_workers;
>>>>           struct btrfs_workqueue *flush_workers;
>>>>           struct workqueue_struct *endio_workers;
>>>> diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
>>>> index 464129b1b0d4..54f2da47c567 100644
>>>> --- a/fs/btrfs/super.c
>>>> +++ b/fs/btrfs/super.c
>>>> @@ -1237,7 +1237,6 @@ static void btrfs_resize_thread_pool(struct btrfs_fs_info *fs_info,
>>>>           btrfs_info(fs_info, "resize thread pool %d -> %d",
>>>>                  old_pool_size, new_pool_size);
>>>>
>>>> -       btrfs_workqueue_set_max(fs_info->workers, new_pool_size);
>>>>           btrfs_workqueue_set_max(fs_info->delalloc_workers, new_pool_size);
>>>>           btrfs_workqueue_set_max(fs_info->caching_workers, new_pool_size);
>>>>           workqueue_set_max_active(fs_info->endio_workers, new_pool_size);
>>>> --
>>>> 2.55.0
>>>>
>>>
>>


  parent reply	other threads:[~2026-09-05  0:57 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-03  8:39 [PATCH] btrfs: move async csum generation out of experimental features Qu Wenruo
2026-09-03  9:20 ` Daniel Vacek
2026-09-03 11:27   ` Qu Wenruo
2026-09-03 12:44     ` Daniel Vacek
2026-09-03 13:07       ` Daniel Vacek
2026-09-05  0:57       ` Qu Wenruo [this message]
2026-09-05 10:09         ` Daniel Vacek
2026-09-05 10:47           ` Qu Wenruo
2026-09-07  9:53             ` Daniel Vacek
2026-09-04 16:53 ` David Sterba
2026-09-04 16:55 ` David Sterba
2026-09-04 17:00 ` David Sterba
2026-09-04 22:21   ` Qu Wenruo
2026-09-07  9:59     ` David Sterba

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=130fdc9a-2d51-49cd-bf7f-c8e4b8fa19aa@suse.com \
    --to=wqu@suse.com \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=neelx@suse.com \
    --cc=quwenruo.btrfs@gmx.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox