From: Qu Wenruo <wqu@suse.com>
To: Filipe Manana <fdmanana@kernel.org>, Josef Bacik <josef@toxicpanda.com>
Cc: David Sterba <dsterba@suse.com>, Chris Mason <mason@kernel.org>,
Naohiro Aota <naohiro.aota@wdc.com>,
Johannes Thumshirn <johannes.thumshirn@wdc.com>,
Boris Burkov <boris@bur.io>,
linux-btrfs@vger.kernel.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: Re: [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync
Date: Wed, 11 Nov 2026 07:44:53 +1030 [thread overview]
Message-ID: <04ce4048-ed7c-4de3-9224-a3e2a9fdd867@suse.com> (raw)
In-Reply-To: <CAL3q7H6B9=fL2_Lx5OzWLRtFXNqfgnTn=kCf4NnE8JktuhSDKw@mail.gmail.com>
在 2026/11/4 04:40, Filipe Manana 写道:
> On Tue, Nov 3, 2026 at 3:57 PM Josef Bacik <josef@toxicpanda.com> wrote:
>>
>> A fast fsync waits for writeback and then checks
>> BTRFS_INODE_COW_WRITE_ERROR to see whether a COW write failed. If one
>> did, it must wait for the ordered extents to complete before logging,
>> since until then the extent maps of the failed range point to extents
>> that were never written. Logging them puts unwritten extents in place
>> of the data that was there before when the log is replayed.
>>
>> The flag can be set too late for that check. end_bbio_data_write()
>> clears writeback before it finishes the ordered extent:
>>
>> end_bbio_data_write() fsync
>>
>> btrfs_folio_clear_writeback()
>> filemap_fdatawait_range()
>> BTRFS_INODE_COW_WRITE_ERROR clear
>> btrfs_log_changed_extents()
>> btrfs_finish_ordered_extent()
>> set BTRFS_INODE_COW_WRITE_ERROR
>>
>> The write paths that fail before submitting a bio, writepage_delalloc()
>> and submit_write_sector(), clear writeback and finish the ordered
>> extent through btrfs_mark_ordered_io_finished(), which never sets the
>> flag at all.
>>
>> Fix by dropping the flag and having a fast fsync wait for
>> BTRFS_ORDERED_IO_DONE on the ordered extents it collected for logging.
>> can_finish_ordered_extent() sets it only after recording any error with
>> BTRFS_ORDERED_IOERR. The wait costs little, because IO_DONE is set
>> right after writeback is cleared, not when the ordered extent completes.
>>
>> Fixes: f13e01b89daf ("btrfs: ensure fast fsync waits for ordered extents after a write failure")
>> Fixes: a11d6912fdd9 ("btrfs: remove folio parameter from ordered io related functions")
In fact, if we merge this one, it will also fix the encoded write bug.
I think if we go this path, I can revert the fix of "btrfs: skip fast
sync for inodes with encoded writes".
Thanks,
Qu
>> CC: stable@vger.kernel.org # 6.10+
>> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
>
> The idea looks sound to me.
>
> Sashiko found something and from a quick glance it seems valid to me:
>
> https://sashiko.dev/#/patchset/20261103-b4-btrfs-data-integrity-v2-0-577dc2690061%40toxicpanda.com
>
> Thanks.
>
>
>> ---
>> fs/btrfs/btrfs_inode.h | 10 ----------
>> fs/btrfs/file.c | 52 +++++++++++++++++++++++++++++++++++++++----------
>> fs/btrfs/ordered-data.c | 31 -----------------------------
>> 3 files changed, 42 insertions(+), 51 deletions(-)
>>
>> diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
>> index f7aa0d145185..65ba8ec23258 100644
>> --- a/fs/btrfs/btrfs_inode.h
>> +++ b/fs/btrfs/btrfs_inode.h
>> @@ -89,16 +89,6 @@ enum {
>> BTRFS_INODE_FREE_SPACE_INODE,
>> /* Set when there are no capabilities in XATTs for the inode. */
>> BTRFS_INODE_NO_CAP_XATTR,
>> - /*
>> - * Set if an error happened when doing a COW write before submitting a
>> - * bio or during writeback. Used for both buffered writes and direct IO
>> - * writes. This is to signal a fast fsync that it has to wait for
>> - * ordered extents to complete and therefore not log extent maps that
>> - * point to unwritten extents (when an ordered extent completes and it
>> - * has the BTRFS_ORDERED_IOERR flag set, it drops extent maps in its
>> - * range).
>> - */
>> - BTRFS_INODE_COW_WRITE_ERROR,
>> /*
>> * Set by reflink while it holds the source's VFS inode lock shared.
>> * Direct IO writes also take that lock shared; one that finds this bit
>> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
>> index f978c6524aa0..4ae9aed8993f 100644
>> --- a/fs/btrfs/file.c
>> +++ b/fs/btrfs/file.c
>> @@ -1512,6 +1512,42 @@ static inline bool skip_inode_logging(const struct btrfs_log_ctx *ctx)
>> return false;
>> }
>>
>> +/*
>> + * Wait for the IO of the ordered extents collected for logging to be done, and
>> + * return true if a COW write among them failed.
>> + *
>> + * Waiting for writeback is not enough to know that: the end IO handlers clear
>> + * writeback before they account the IO in the ordered extent. An ordered
>> + * extent gets BTRFS_ORDERED_IO_DONE only once all of its IO is accounted, and
>> + * can_finish_ordered_extent() sets BTRFS_ORDERED_IOERR before that.
>> + */
>> +static bool cow_write_failed(struct btrfs_inode *inode,
>> + struct list_head *ordered_extents)
>> +{
>> + struct btrfs_ordered_extent *ordered;
>> + bool failed = false;
>> +
>> + list_for_each_entry(ordered, ordered_extents, log_list)
>> + wait_event(ordered->wait,
>> + test_bit(BTRFS_ORDERED_IO_DONE, &ordered->flags));
>> +
>> + /*
>> + * can_finish_ordered_extent() records a write error before it sets
>> + * BTRFS_ORDERED_IO_DONE, both under this lock.
>> + */
>> + spin_lock(&inode->ordered_tree_lock);
>> + list_for_each_entry(ordered, ordered_extents, log_list) {
>> + if (test_bit(BTRFS_ORDERED_IOERR, &ordered->flags) &&
>> + !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags)) {
>> + failed = true;
>> + break;
>> + }
>> + }
>> + spin_unlock(&inode->ordered_tree_lock);
>> +
>> + return failed;
>> +}
>> +
>> /*
>> * fsync call for both files and directories. This logs the inode into
>> * the tree log instead of forcing full commits whenever possible.
>> @@ -1615,7 +1651,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
>> * IO of a lower priority task while holding a transaction open.
>> *
>> * For a full fsync we wait for the ordered extents to complete while
>> - * for a fast fsync we wait just for writeback to complete, and then
>> + * for a fast fsync we wait just for their IO to complete, and then
>> * attach the ordered extents to the transaction so that a transaction
>> * commit waits for their completion, to avoid data loss if we fsync,
>> * the current transaction commits before the ordered extents complete
>> @@ -1627,7 +1663,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
>> */
>> if (full_sync || btrfs_is_zoned(fs_info)) {
>> ret = btrfs_wait_ordered_range(inode, start, len);
>> - clear_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
>> } else {
>> /*
>> * Get our ordered extents as soon as possible to avoid doing
>> @@ -1640,15 +1675,12 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
>> goto out_release_extents;
>>
>> /*
>> - * Check and clear the BTRFS_INODE_COW_WRITE_ERROR now after
>> - * starting and waiting for writeback, because for buffered IO
>> - * it may have been set during the end IO callback
>> - * (end_bbio_data_write() -> btrfs_finish_ordered_extent()) in
>> - * case an error happened and we need to wait for ordered
>> - * extents to complete so that any extent maps that point to
>> - * unwritten locations are dropped and we don't log them.
>> + * If a COW write failed, before submitting a bio or during IO,
>> + * the extent maps of its range point to unwritten locations
>> + * until the ordered extent completes and drops them. Wait for
>> + * that, so that we don't log them.
>> */
>> - if (test_and_clear_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags))
>> + if (cow_write_failed(inode, &ctx.ordered_extents))
>> ret = btrfs_wait_ordered_range(inode, start, len);
>> }
>>
>> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
>> index df74c75d6c29..27aca31866fd 100644
>> --- a/fs/btrfs/ordered-data.c
>> +++ b/fs/btrfs/ordered-data.c
>> @@ -436,37 +436,6 @@ void btrfs_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
>> uptodate);
>> spin_unlock(&inode->ordered_tree_lock);
>>
>> - /*
>> - * If this is a COW write it means we created new extent maps for the
>> - * range and they point to unwritten locations if we got an error either
>> - * before submitting a bio or during IO.
>> - *
>> - * We have marked the ordered extent with BTRFS_ORDERED_IOERR, and we
>> - * are queuing its completion below. During completion, at
>> - * btrfs_finish_one_ordered(), we will drop the extent maps for the
>> - * unwritten extents.
>> - *
>> - * However because completion runs in a work queue we can end up having
>> - * a fast fsync running before that. In the case of direct IO, once we
>> - * unlock the inode the fsync might start, and we queue the completion
>> - * before unlocking the inode. In the case of buffered IO when writeback
>> - * finishes (end_bbio_data_write()) we queue the completion, so if the
>> - * writeback was triggered by a fast fsync, the fsync might start
>> - * logging before ordered extent completion runs in the work queue.
>> - *
>> - * The fast fsync will log file extent items based on the extent maps it
>> - * finds, so if by the time it collects extent maps the ordered extent
>> - * completion didn't happen yet, it will log file extent items that
>> - * point to unwritten extents, resulting in a corruption if a crash
>> - * happens and the log tree is replayed. Note that a fast fsync does not
>> - * wait for completion of ordered extents in order to reduce latency.
>> - *
>> - * Set a flag in the inode so that the next fast fsync will wait for
>> - * ordered extents to complete before starting to log.
>> - */
>> - if (!uptodate && !test_bit(BTRFS_ORDERED_NOCOW, &ordered->flags))
>> - set_bit(BTRFS_INODE_COW_WRITE_ERROR, &inode->runtime_flags);
>> -
>> if (ret)
>> btrfs_queue_ordered_fn(ordered);
>> }
>>
>> --
>> 2.55.0
>>
>>
next prev parent reply other threads:[~2026-11-10 21:15 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 15:05 [PATCH v2 0/5] btrfs: data integrity fixes Josef Bacik
2026-11-03 15:05 ` [PATCH v2 1/5] btrfs: don't drop the error of the last split bio Josef Bacik
2026-11-03 15:05 ` [PATCH v2 2/5] btrfs: clear dirty blocks after a delalloc failure Josef Bacik
2026-11-03 15:05 ` [PATCH v2 3/5] btrfs: wait for ordered extent IO in fast fsync Josef Bacik
2026-11-03 18:10 ` Filipe Manana
2026-11-10 21:14 ` Qu Wenruo [this message]
2026-11-03 15:05 ` [PATCH v2 4/5] btrfs: don't report success for a skipped log sync Josef Bacik
2026-11-03 15:05 ` [PATCH v2 5/5] btrfs: don't trim the target of a device replace Josef Bacik
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=04ce4048-ed7c-4de3-9224-a3e2a9fdd867@suse.com \
--to=wqu@suse.com \
--cc=boris@bur.io \
--cc=dsterba@suse.com \
--cc=fdmanana@kernel.org \
--cc=johannes.thumshirn@wdc.com \
--cc=josef@toxicpanda.com \
--cc=linux-btrfs@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mason@kernel.org \
--cc=naohiro.aota@wdc.com \
--cc=stable@vger.kernel.org \
/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
all inboxes | Powered by JetHome®