mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO
@ 2026-06-25  2:14 Yun Zhou
  2026-06-25  5:13 ` Qu Wenruo
  0 siblings, 1 reply; 5+ messages in thread
From: Yun Zhou @ 2026-06-25  2:14 UTC (permalink / raw)
  To: clm, dsterba; +Cc: linux-btrfs, linux-kernel, yun.zhou

When btrfs_direct_write() falls back to buffered IO after a failed DIO
attempt, it may race with the asynchronous completion of DIO ordered
extents.  This leads to a BUG_ON in insert_ordered_extent() due to
overlapping ordered extents in the per-inode rb-tree.

The race sequence is:
 1. DIO creates an ordered extent via btrfs_dio_iomap_begin()
 2. Page fault occurs (nofault=true), no bio is submitted (submitted=0)
 3. btrfs_dio_iomap_end() truncates and finishes the OE asynchronously
    via btrfs_finish_ordered_extent() which queues work
 4. iomap returns 0, retry logic faults in pages and retries DIO
 5. Second DIO attempt also fails, code reaches buffered: label
 6. btrfs_buffered_write() dirties pages for the same range
 7. btrfs_fdatawrite_range() triggers writeback
 8. run_delalloc_nocow() -> fallback_to_cow() -> cow_file_range()
    tries to insert a new ordered extent for the same file offset
 9. The DIO ordered extent hasn't been removed from the rb-tree yet
    (btrfs_finish_ordered_io running async in workqueue) -> BUG_ON

Fix this by waiting for any pending ordered extents in the target range
before starting the buffered write.

Reported-by: syzbot+ba2afde329fc27e3f22e@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=ba2afde329fc27e3f22e
Fixes: acf9ed3a6c00 ("btrfs: retry faulting in the pages after a zero sized short direct write")
Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
---
 fs/btrfs/direct-io.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 460326d34143..e8ac9492844c 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -844,6 +844,7 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from)
 	struct file *file = iocb->ki_filp;
 	struct inode *inode = file_inode(file);
 	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
+	struct btrfs_ordered_extent *ordered;
 	loff_t pos;
 	ssize_t written = 0;
 	ssize_t written_buffered;
@@ -1025,6 +1026,29 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from)
 	}
 
 	pos = iocb->ki_pos;
+
+	/*
+	 * The DIO path may have created ordered extent(s) that are still being
+	 * processed asynchronously in a work queue.  We must wait for them to
+	 * be fully completed and removed from the rb-tree before doing a
+	 * buffered write to the same or overlapping range; otherwise the
+	 * buffered writeback path (run_delalloc_nocow -> fallback_to_cow ->
+	 * cow_file_range) may try to insert a new ordered extent that conflicts
+	 * with the still-pending DIO one, triggering a BUG_ON in
+	 * insert_ordered_extent().
+	 *
+	 * This happens when DIO creates an ordered extent but has a short write
+	 * (submitted < length in btrfs_dio_iomap_end()), which truncates and
+	 * finishes the ordered extent asynchronously while we fall back to
+	 * buffered IO for the same range.
+	 */
+	while ((ordered = btrfs_lookup_ordered_range(BTRFS_I(inode),
+				(u64)(pos - written),
+				(u64)written + iov_iter_count(from))) != NULL) {
+		btrfs_start_ordered_extent(ordered);
+		btrfs_put_ordered_extent(ordered);
+	}
+
 	written_buffered = btrfs_buffered_write(iocb, from);
 	if (written_buffered < 0) {
 		ret = written_buffered;
-- 
2.43.0


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

* Re: [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO
  2026-06-25  2:14 [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO Yun Zhou
@ 2026-06-25  5:13 ` Qu Wenruo
  2026-06-25  5:17   ` Qu Wenruo
  0 siblings, 1 reply; 5+ messages in thread
From: Qu Wenruo @ 2026-06-25  5:13 UTC (permalink / raw)
  To: Yun Zhou, clm, dsterba; +Cc: linux-btrfs, linux-kernel



在 2026/6/25 11:44, Yun Zhou 写道:
> When btrfs_direct_write() falls back to buffered IO after a failed DIO
> attempt, it may race with the asynchronous completion of DIO ordered
> extents.  This leads to a BUG_ON in insert_ordered_extent() due to
> overlapping ordered extents in the per-inode rb-tree.
> 
> The race sequence is:
>   1. DIO creates an ordered extent via btrfs_dio_iomap_begin()
>   2. Page fault occurs (nofault=true), no bio is submitted (submitted=0)
>   3. btrfs_dio_iomap_end() truncates and finishes the OE asynchronously
>      via btrfs_finish_ordered_extent() which queues work
>   4. iomap returns 0, retry logic faults in pages and retries DIO
>   5. Second DIO attempt also fails, code reaches buffered: label
>   6. btrfs_buffered_write() dirties pages for the same range

btrfs_buffered_write()
|- copy_one_range()
    |- lock_and_cleanup_extent_if_needed()
       |- btrfs_start_ordered_extent()

So your explanation doesn't makes sense. As if there is the direct IO oe 
remaining, we will wait for that OE to complete.

There is still something missing.

>   7. btrfs_fdatawrite_range() triggers writeback
>   8. run_delalloc_nocow() -> fallback_to_cow() -> cow_file_range()
>      tries to insert a new ordered extent for the same file offset
>   9. The DIO ordered extent hasn't been removed from the rb-tree yet
>      (btrfs_finish_ordered_io running async in workqueue) -> BUG_ON
> 
> Fix this by waiting for any pending ordered extents in the target range
> before starting the buffered write.
> 
> Reported-by: syzbot+ba2afde329fc27e3f22e@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=ba2afde329fc27e3f22e
> Fixes: acf9ed3a6c00 ("btrfs: retry faulting in the pages after a zero sized short direct write")
> Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
> ---
>   fs/btrfs/direct-io.c | 24 ++++++++++++++++++++++++
>   1 file changed, 24 insertions(+)
> 
> diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
> index 460326d34143..e8ac9492844c 100644
> --- a/fs/btrfs/direct-io.c
> +++ b/fs/btrfs/direct-io.c
> @@ -844,6 +844,7 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from)
>   	struct file *file = iocb->ki_filp;
>   	struct inode *inode = file_inode(file);
>   	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
> +	struct btrfs_ordered_extent *ordered;
>   	loff_t pos;
>   	ssize_t written = 0;
>   	ssize_t written_buffered;
> @@ -1025,6 +1026,29 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from)
>   	}
>   
>   	pos = iocb->ki_pos;
> +
> +	/*
> +	 * The DIO path may have created ordered extent(s) that are still being
> +	 * processed asynchronously in a work queue.  We must wait for them to
> +	 * be fully completed and removed from the rb-tree before doing a
> +	 * buffered write to the same or overlapping range; otherwise the
> +	 * buffered writeback path (run_delalloc_nocow -> fallback_to_cow ->
> +	 * cow_file_range) may try to insert a new ordered extent that conflicts
> +	 * with the still-pending DIO one, triggering a BUG_ON in
> +	 * insert_ordered_extent().
> +	 *
> +	 * This happens when DIO creates an ordered extent but has a short write
> +	 * (submitted < length in btrfs_dio_iomap_end()), which truncates and
> +	 * finishes the ordered extent asynchronously while we fall back to
> +	 * buffered IO for the same range.
> +	 */
> +	while ((ordered = btrfs_lookup_ordered_range(BTRFS_I(inode),
> +				(u64)(pos - written),
> +				(u64)written + iov_iter_count(from))) != NULL) {
> +		btrfs_start_ordered_extent(ordered);
> +		btrfs_put_ordered_extent(ordered);
> +	}
> +
>   	written_buffered = btrfs_buffered_write(iocb, from);
>   	if (written_buffered < 0) {
>   		ret = written_buffered;


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

* Re: [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO
  2026-06-25  5:13 ` Qu Wenruo
@ 2026-06-25  5:17   ` Qu Wenruo
  2026-06-25  6:46     ` Zhou, Yun
  0 siblings, 1 reply; 5+ messages in thread
From: Qu Wenruo @ 2026-06-25  5:17 UTC (permalink / raw)
  To: Yun Zhou, clm, dsterba; +Cc: linux-btrfs, linux-kernel



在 2026/6/25 14:43, Qu Wenruo 写道:
> 
> 
> 在 2026/6/25 11:44, Yun Zhou 写道:
>> When btrfs_direct_write() falls back to buffered IO after a failed DIO
>> attempt, it may race with the asynchronous completion of DIO ordered
>> extents.  This leads to a BUG_ON in insert_ordered_extent() due to
>> overlapping ordered extents in the per-inode rb-tree.
>>
>> The race sequence is:
>>   1. DIO creates an ordered extent via btrfs_dio_iomap_begin()
>>   2. Page fault occurs (nofault=true), no bio is submitted (submitted=0)
>>   3. btrfs_dio_iomap_end() truncates and finishes the OE asynchronously
>>      via btrfs_finish_ordered_extent() which queues work
>>   4. iomap returns 0, retry logic faults in pages and retries DIO
>>   5. Second DIO attempt also fails, code reaches buffered: label
>>   6. btrfs_buffered_write() dirties pages for the same range
> 
> btrfs_buffered_write()
> |- copy_one_range()
>     |- lock_and_cleanup_extent_if_needed()
>        |- btrfs_start_ordered_extent()
> 
> So your explanation doesn't makes sense. As if there is the direct IO oe 
> remaining, we will wait for that OE to complete.
> 
> There is still something missing.
> 
>>   7. btrfs_fdatawrite_range() triggers writeback
>>   8. run_delalloc_nocow() -> fallback_to_cow() -> cow_file_range()
>>      tries to insert a new ordered extent for the same file offset
>>   9. The DIO ordered extent hasn't been removed from the rb-tree yet
>>      (btrfs_finish_ordered_io running async in workqueue) -> BUG_ON
>>
>> Fix this by waiting for any pending ordered extents in the target range
>> before starting the buffered write.
>>
>> Reported-by: syzbot+ba2afde329fc27e3f22e@syzkaller.appspotmail.com
>> Closes: https://syzkaller.appspot.com/bug?extid=ba2afde329fc27e3f22e
>> Fixes: acf9ed3a6c00 ("btrfs: retry faulting in the pages after a zero 
>> sized short direct write")

And the fixes tag is also incorrect.

Without that commit, we will directly fallback to buffered write without 
retry faulting in the pages.

So by your explanation it will trigger the same problem, with or without 
that commit.

>> Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
>> ---
>>   fs/btrfs/direct-io.c | 24 ++++++++++++++++++++++++
>>   1 file changed, 24 insertions(+)
>>
>> diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
>> index 460326d34143..e8ac9492844c 100644
>> --- a/fs/btrfs/direct-io.c
>> +++ b/fs/btrfs/direct-io.c
>> @@ -844,6 +844,7 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, 
>> struct iov_iter *from)
>>       struct file *file = iocb->ki_filp;
>>       struct inode *inode = file_inode(file);
>>       struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
>> +    struct btrfs_ordered_extent *ordered;
>>       loff_t pos;
>>       ssize_t written = 0;
>>       ssize_t written_buffered;
>> @@ -1025,6 +1026,29 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, 
>> struct iov_iter *from)
>>       }
>>       pos = iocb->ki_pos;
>> +
>> +    /*
>> +     * The DIO path may have created ordered extent(s) that are still 
>> being
>> +     * processed asynchronously in a work queue.  We must wait for 
>> them to
>> +     * be fully completed and removed from the rb-tree before doing a
>> +     * buffered write to the same or overlapping range; otherwise the
>> +     * buffered writeback path (run_delalloc_nocow -> fallback_to_cow ->
>> +     * cow_file_range) may try to insert a new ordered extent that 
>> conflicts
>> +     * with the still-pending DIO one, triggering a BUG_ON in
>> +     * insert_ordered_extent().
>> +     *
>> +     * This happens when DIO creates an ordered extent but has a 
>> short write
>> +     * (submitted < length in btrfs_dio_iomap_end()), which truncates 
>> and
>> +     * finishes the ordered extent asynchronously while we fall back to
>> +     * buffered IO for the same range.
>> +     */
>> +    while ((ordered = btrfs_lookup_ordered_range(BTRFS_I(inode),
>> +                (u64)(pos - written),
>> +                (u64)written + iov_iter_count(from))) != NULL) {
>> +        btrfs_start_ordered_extent(ordered);
>> +        btrfs_put_ordered_extent(ordered);
>> +    }
>> +
>>       written_buffered = btrfs_buffered_write(iocb, from);
>>       if (written_buffered < 0) {
>>           ret = written_buffered;
> 
> 


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

* Re: [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO
  2026-06-25  5:17   ` Qu Wenruo
@ 2026-06-25  6:46     ` Zhou, Yun
  2026-06-25  6:51       ` Qu Wenruo
  0 siblings, 1 reply; 5+ messages in thread
From: Zhou, Yun @ 2026-06-25  6:46 UTC (permalink / raw)
  To: Qu Wenruo, clm, dsterba; +Cc: linux-btrfs, linux-kernel



On 6/25/26 13:17, Qu Wenruo wrote:
> 
> 在 2026/6/25 14:43, Qu Wenruo 写道:
>>
>>
>> 在 2026/6/25 11:44, Yun Zhou 写道:
>>> When btrfs_direct_write() falls back to buffered IO after a failed DIO
>>> attempt, it may race with the asynchronous completion of DIO ordered
>>> extents.  This leads to a BUG_ON in insert_ordered_extent() due to
>>> overlapping ordered extents in the per-inode rb-tree.
>>>
>>> The race sequence is:
>>>   1. DIO creates an ordered extent via btrfs_dio_iomap_begin()
>>>   2. Page fault occurs (nofault=true), no bio is submitted (submitted=0)
>>>   3. btrfs_dio_iomap_end() truncates and finishes the OE asynchronously
>>>      via btrfs_finish_ordered_extent() which queues work
>>>   4. iomap returns 0, retry logic faults in pages and retries DIO
>>>   5. Second DIO attempt also fails, code reaches buffered: label
>>>   6. btrfs_buffered_write() dirties pages for the same range
>>
>> btrfs_buffered_write()
>> |- copy_one_range()
>>     |- lock_and_cleanup_extent_if_needed()
>>        |- btrfs_start_ordered_extent()
>>
>> So your explanation doesn't makes sense. As if there is the direct IO oe
>> remaining, we will wait for that OE to complete.
>>
>> There is still something missing.
>>
>>>   7. btrfs_fdatawrite_range() triggers writeback
>>>   8. run_delalloc_nocow() -> fallback_to_cow() -> cow_file_range()
>>>      tries to insert a new ordered extent for the same file offset
>>>   9. The DIO ordered extent hasn't been removed from the rb-tree yet
>>>      (btrfs_finish_ordered_io running async in workqueue) -> BUG_ON
>>>
>>> Fix this by waiting for any pending ordered extents in the target range
>>> before starting the buffered write.
>>>
>>> Reported-by: syzbot+ba2afde329fc27e3f22e@syzkaller.appspotmail.com
>>> Closes: https://syzkaller.appspot.com/bug?extid=ba2afde329fc27e3f22e
>>> Fixes: acf9ed3a6c00 ("btrfs: retry faulting in the pages after a zero
>>> sized short direct write")
> 
> And the fixes tag is also incorrect.
> 
> Without that commit, we will directly fallback to buffered write without
> retry faulting in the pages.
> 
> So by your explanation it will trigger the same problem, with or without
> that commit.

Yes, my previous analysis does seem inaccurate. Commit acf9ed3a6c00 (which
added retries) merely amplified the window for the issue to occur, but 
the problem has actually existed since ff66fe666233 ('btrfs: fix 
incorrect buffered IO fallback for append direct writes'), which 
introduced i_size revert on DIO short writes, causing 
lock_and_cleanup_extent_if_need() to skip the OE check (since start_pos 
 >= reverted i_size). I will correct the commit message and the Fixes 
tag in v2.

Thanks,
Yun

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

* Re: [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO
  2026-06-25  6:46     ` Zhou, Yun
@ 2026-06-25  6:51       ` Qu Wenruo
  0 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-06-25  6:51 UTC (permalink / raw)
  To: Zhou, Yun, clm, dsterba; +Cc: linux-btrfs, linux-kernel



在 2026/6/25 16:16, Zhou, Yun 写道:
> 
> 
> On 6/25/26 13:17, Qu Wenruo wrote:
>>
>> 在 2026/6/25 14:43, Qu Wenruo 写道:
>>>
>>>
>>> 在 2026/6/25 11:44, Yun Zhou 写道:
>>>> When btrfs_direct_write() falls back to buffered IO after a failed DIO
>>>> attempt, it may race with the asynchronous completion of DIO ordered
>>>> extents.  This leads to a BUG_ON in insert_ordered_extent() due to
>>>> overlapping ordered extents in the per-inode rb-tree.
>>>>
>>>> The race sequence is:
>>>>   1. DIO creates an ordered extent via btrfs_dio_iomap_begin()
>>>>   2. Page fault occurs (nofault=true), no bio is submitted 
>>>> (submitted=0)
>>>>   3. btrfs_dio_iomap_end() truncates and finishes the OE asynchronously
>>>>      via btrfs_finish_ordered_extent() which queues work
>>>>   4. iomap returns 0, retry logic faults in pages and retries DIO
>>>>   5. Second DIO attempt also fails, code reaches buffered: label
>>>>   6. btrfs_buffered_write() dirties pages for the same range
>>>
>>> btrfs_buffered_write()
>>> |- copy_one_range()
>>>     |- lock_and_cleanup_extent_if_needed()
>>>        |- btrfs_start_ordered_extent()
>>>
>>> So your explanation doesn't makes sense. As if there is the direct IO oe
>>> remaining, we will wait for that OE to complete.
>>>
>>> There is still something missing.
>>>
>>>>   7. btrfs_fdatawrite_range() triggers writeback
>>>>   8. run_delalloc_nocow() -> fallback_to_cow() -> cow_file_range()
>>>>      tries to insert a new ordered extent for the same file offset
>>>>   9. The DIO ordered extent hasn't been removed from the rb-tree yet
>>>>      (btrfs_finish_ordered_io running async in workqueue) -> BUG_ON
>>>>
>>>> Fix this by waiting for any pending ordered extents in the target range
>>>> before starting the buffered write.
>>>>
>>>> Reported-by: syzbot+ba2afde329fc27e3f22e@syzkaller.appspotmail.com
>>>> Closes: https://syzkaller.appspot.com/bug?extid=ba2afde329fc27e3f22e
>>>> Fixes: acf9ed3a6c00 ("btrfs: retry faulting in the pages after a zero
>>>> sized short direct write")
>>
>> And the fixes tag is also incorrect.
>>
>> Without that commit, we will directly fallback to buffered write without
>> retry faulting in the pages.
>>
>> So by your explanation it will trigger the same problem, with or without
>> that commit.
> 
> Yes, my previous analysis does seem inaccurate. Commit acf9ed3a6c00 (which
> added retries) merely amplified the window for the issue to occur, but 
> the problem has actually existed since ff66fe666233 ('btrfs: fix 
> incorrect buffered IO fallback for append direct writes'), which 
> introduced i_size revert on DIO short writes, causing 
> lock_and_cleanup_extent_if_need() to skip the OE check (since start_pos 
>  >= reverted i_size). I will correct the commit message and the Fixes 
> tag in v2.

That's right, and I have already sent out a fix based on that isize revert:

https://lore.kernel.org/linux-btrfs/aaabe0f2bee07800fb0490d24fab5cd162e5241d.1782366056.git.wqu@suse.com/

> 
> Thanks,
> Yun


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

end of thread, other threads:[~2026-06-25  6:51 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-25  2:14 [PATCH] btrfs: wait for ordered extents before buffered write fallback in direct IO Yun Zhou
2026-06-25  5:13 ` Qu Wenruo
2026-06-25  5:17   ` Qu Wenruo
2026-06-25  6:46     ` Zhou, Yun
2026-06-25  6:51       ` Qu Wenruo

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®