From: Qu Wenruo <quwenruo.btrfs@gmx.com>
To: Dave Chinner <david@fromorbit.com>
Cc: "linux-fsdevel@vger.kernel.org" <linux-fsdevel@vger.kernel.org>,
linux-xfs@vger.kernel.org,
linux-btrfs <linux-btrfs@vger.kernel.org>,
Christian Brauner <brauner@kernel.org>
Subject: Re: Iomap buffered write short copy handling (with full folio uptodate)
Date: Fri, 21 Mar 2025 19:46:11 +1030 [thread overview]
Message-ID: <65a02281-bd7d-4b34-a8a2-97af052da301@gmx.com> (raw)
In-Reply-To: <Z90p3fep5m8Lxv7d@dread.disaster.area>
在 2025/3/21 19:27, Dave Chinner 写道:
> On Fri, Mar 21, 2025 at 06:42:25PM +1030, Qu Wenruo wrote:
>> Hi,
>>
>> I'm wondering if the current iomap short copy handler can handle the
>> following case correctly:
>>
>> The fs block size is 4K, page size is 4K, the buffered write is into
>> file range [0, 4K), the fs is always doing data COW.
>>
>> The folio at file offset 0 is already uptodate, and the folio size is
>> also 4K.
>>
>> - ops->iomap_begin() got called for the range [0, 4K) from iomap_iter()
>> The fs reserved space of one block of data, and some extra metadata
>> space.
>>
>> - copy_folio_from_iter_atomic() only copied 1K bytes
>>
>> - iomap_write_end() returned true
>> Since the folio is already uptodate, we can handle the short copy.
>> The folio is marked dirty and uptodate.
>>
>> - __iomap_put_folio() unlocked and put the folio
>>
>> - Now a writeback was triggered for that folio at file offset 0
>> The folio got properly written to disk.
>>
>> But remember we have only reserved one block of data space, and that
>> reserved space is consumed by this writeback.
>
> This bumps the internal inode mapping generation number....
>
>> What's worse is, the fs can even do a snapshot of that involved inode,
>> so that the current copy of that 1K short-written block will not be
>> freed.
>>
>> - copy_folio_from_iter_atomic() copied the remaining 3K bytes
>
> No, we don't get that far. iomap_begin_write() calls
> __iomap_get_folio() to get and lock the folio again, then calls
> folio_ops->iomap_valid() to check that the iomap is still valid.
>
> In the above case, the cookie in the iomap (the mapping generation
> number at the time the iomap was created by ->iomap_begin) won't
> match the current inode mapping generation number as it was bumped
> on writeback.
>
> Hence the iomap is marked IOMAP_F_STALE, the current write is
> aborted before it starts, then iomap_write_iter() sees IOMAP_F_STALE
> and restarts the write again.
>
> We then get a new mapping from ops->iomap_begin() with a new 1 block
> reservation for the remaining 3kB of data to be copied into that
> block.
>
> i.e. iomaps are cached information, and we have to validate that the
> mapping has not changed once we have all the objects we are about to
> modify locked and ready for modification.
Thanks a lot!
Didn't notice the iomap_valid() handling is even involved.
>
>> All these happens inside the do {} while () loop of
>> iomap_write_iter(), thus no iomap_begin() callback can be triggered to
>> allocate extra space.
>>
>> - __iomap_put_folio() unlocked and put the folio 0 again.
>>
>> - Now a writeback got started for that folio at file offset 0 again
>> This requires another free data block from the fs.
>>
>> In that case, iomap_begin() only reserved one block of data.
>> But in the end, we wrote 2 blocks of data due to short copy.
>>
>> I'm wondering what's the proper handling of short copy during buffered
>> write.
>
>> Is there any special locking I missed preventing the folio from being
>> written back halfway?
>
> Not locking, just state validation and IOMAP_F_STALE. i.e.
> filesystems that use delalloc or cow absolutely need to implement
> folio_ops->iomap_valid() to detect stale iomaps....
Got it, this also means a COW fs must implement that callback if using
iomap.
And this solution also has a good optimization where if no writeback
happened between the lock and unlock, we can just reuse the last
reserved space without extra allocation.
Let me explore if we can do a similar solution in btrfs, other than the
current always-re-allocate behavior.
Thanks,
Qu
>
>> Or is it just too hard to trigger such case in the real world?
>
> Triggered it, we certainly did. It caused data corruption and took
> quite some time to triage and understand.
>
> -Dave.
prev parent reply other threads:[~2025-03-21 9:16 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-21 8:12 Iomap buffered write short copy handling (with full folio uptodate) Qu Wenruo
2025-03-21 8:57 ` Dave Chinner
2025-03-21 9:16 ` Qu Wenruo [this message]
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=65a02281-bd7d-4b34-a8a2-97af052da301@gmx.com \
--to=quwenruo.btrfs@gmx.com \
--cc=brauner@kernel.org \
--cc=david@fromorbit.com \
--cc=linux-btrfs@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-xfs@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