Re: [PATCH] btrfs: always wait for ordered extents to avoid OE races

Filipe Manana <[email protected]>
Newsgroups org.kernel.vger.linux-btrfs
Message-ID <CAL3q7H6sNSPqkE+h7PgX+Vv+7o5zTokBi52F5SooMmxEEH-phQ@mail.gmail.com>
On Thu, Jun 25, 2026 at 11:39 PM Qu Wenruo <[email protected]> wrote:
>
>
>
> 在 2026/6/25 20:59, Filipe Manana 写道:
> [...]
> >
> >> @@ -896,42 +896,38 @@ lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct folio *folio,
> >>          start_pos = round_down(pos, fs_info->sectorsize);
> >>          last_pos = round_up(pos + write_bytes, fs_info->sectorsize) - 1;
> >>
> >> -       if (start_pos < inode->vfs_inode.i_size) {
> >> -               struct btrfs_ordered_extent *ordered;
> >> -
> >> -               if (nowait) {
> >> -                       if (!btrfs_try_lock_extent(&inode->io_tree, start_pos,
> >> -                                                  last_pos, cached_state)) {
> >> -                               folio_unlock(folio);
> >> -                               folio_put(folio);
> >> -                               return -EAGAIN;
> >> -                       }
> >> -               } else {
> >> -                       btrfs_lock_extent(&inode->io_tree, start_pos, last_pos,
> >> -                                         cached_state);
> >> -               }
> >> -
> >> -               ordered = btrfs_lookup_ordered_range(inode, start_pos,
> >> -                                                    last_pos - start_pos + 1);
> >> -               if (ordered &&
> >> -                   ordered->file_offset + ordered->num_bytes > start_pos &&
> >> -                   ordered->file_offset <= last_pos) {
> >> -                       btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos,
> >> -                                           cached_state);
> >> +       if (nowait) {
> >> +               if (!btrfs_try_lock_extent(&inode->io_tree, start_pos,
> >> +                                          last_pos, cached_state)) {
> >>                          folio_unlock(folio);
> >>                          folio_put(folio);
> >> -                       btrfs_start_ordered_extent(ordered);
> >> -                       btrfs_put_ordered_extent(ordered);
> >>                          return -EAGAIN;
> >>                  }
> >> -               if (ordered)
> >> -                       btrfs_put_ordered_extent(ordered);
> >> -
> >> -               *lockstart = start_pos;
> >> -               *lockend = last_pos;
> >> -               ret = 1;
> >> +       } else {
> >> +               btrfs_lock_extent(&inode->io_tree, start_pos, last_pos,
> >> +                                 cached_state);
> >>          }
> >>
> >> +       ordered = btrfs_lookup_ordered_range(inode, start_pos,
> >> +                                            last_pos - start_pos + 1);
> >> +       if (ordered &&
> >> +           ordered->file_offset + ordered->num_bytes > start_pos &&
> >> +           ordered->file_offset <= last_pos) {
> >> +               btrfs_unlock_extent(&inode->io_tree, start_pos, last_pos,
> >> +                                   cached_state);
> >> +               folio_unlock(folio);
> >> +               folio_put(folio);
> >> +               btrfs_start_ordered_extent(ordered);
> >> +               btrfs_put_ordered_extent(ordered);
> >> +               return -EAGAIN;
> >> +       }
> >> +       if (ordered)
> >> +               btrfs_put_ordered_extent(ordered);
> >> +
> >> +       *lockstart = start_pos;
> >> +       *lockend = last_pos;
> >> +       ret = 1;
> >
> > And this is pointless, right below we can use "return 1;" and get rid
> > of the 'ret' variable.
> >
> > Since returning 1 no longer makes sense, just make it return 0 and remove 'ret'.
> >
> > Also in the caller, since lock_and_cleanup_extent_if_need() now always
> > returns with the extent range locked, we can get rid of it's local
> > variable named 'extents_locked' and use 'ret' as it does not make
> > sense anymore.
> >
> > Finally, with this change we now always look for ordered extents and
> > lock the range. Previously, we only did this if writes were within
> > i_size - which makes sense because, except for his new direct IO case
> > falling back to buffered IO, we could never find ordered extents
> > beyond i_size.
> >
> > Have you checked whether performance is affected?
>
> Here is the microbenchmark for the runtime of btrfs_buffered_write() and
> lock_and_cleanup_extent_if_need().
>
> The workload is a very basic xfs_io -f -c "pwrite 0 1m", which is always
> appending the isize for every 4K block.
>
> Before patch:
> - lock_and_cleanup_extent_if_need():
>
>    runtime avg = 58.16 ns
>    runtime stdev = 4.2
>
> - btrfs_buffered_write():
>
>    runtime avg = 2115.6 ns
>    runtime stdev = 1822.1
>
>
> Patched:
> - lock_and_cleanup_extent_if_need():
>
>    runtime avg = 183.0 ns
>    runtime stdev = 161.6
>
> - btrfs_buffered_write():
>
>    runtime avg = 2973.3 ns
>    runtime stdev = 4225.1
>
> So yes, the runtime of lock_and_cleanup_extent_if_need() has greatly
> increased, by more than 3 times, but still less than 1 micro second.
>
> The btrfs_buffered_write() itself is also much slower due to the extra
> extent unlock, the runtime is increased by 40%, but still less than 3 us.
>
> I'm not sure if the cost is acceptable or not. The increased runtime is
> pretty huge for the affected worst case scenario, and that's undeniable.
>
> But the overall runtime of btrfs_buffered_write() itself is still at
> single digit microsecond level.
>
> If acceptable, I'll add the microbenchmark into the commit message in
> the next update.

Instead of this approach, can we wait for the ordered extent(s) in DIO
path that reverts i_size after the short write?

>
> Thanks,
> Qu
>
> >
> > Thanks.
> >
> >> +
> >>          /*
> >>           * We should be called after prepare_one_folio() which should have locked
> >>           * all pages in the range.
> >> @@ -1288,11 +1284,8 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter,
> >>
> >>                  /* No copied bytes, unlock, release reserved space and exit. */
> >>                  if (copied == 0) {
> >> -                       if (extents_locked)
> >> -                               btrfs_unlock_extent(&inode->io_tree, lockstart, lockend,
> >> -                                                   &cached_state);
> >> -                       else
> >> -                               btrfs_free_extent_state(cached_state);
> >> +                       btrfs_unlock_extent(&inode->io_tree, lockstart, lockend,
> >> +                                           &cached_state);
> >>                          btrfs_delalloc_release_extents(inode, reserved_len);
> >>                          release_space(inode, *data_reserved, reserved_start, reserved_len,
> >>                                        only_release_metadata);
> >> @@ -1311,17 +1304,7 @@ static int copy_one_range(struct btrfs_inode *inode, struct iov_iter *iter,
> >>
> >>          ret = btrfs_dirty_folio(inode, folio, start, copied, &cached_state,
> >>                                  only_release_metadata);
> >> -       /*
> >> -        * If we have not locked the extent range, because the range's start
> >> -        * offset is >= i_size, we might still have a non-NULL cached extent
> >> -        * state, acquired while marking the extent range as delalloc through
> >> -        * btrfs_dirty_page(). Therefore free any possible cached extent state
> >> -        * to avoid a memory leak.
> >> -        */
> >> -       if (extents_locked)
> >> -               btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state);
> >> -       else
> >> -               btrfs_free_extent_state(cached_state);
> >> +       btrfs_unlock_extent(&inode->io_tree, lockstart, lockend, &cached_state);
> >>
> >>          btrfs_delalloc_release_extents(inode, reserved_len);
> >>          if (ret) {
> >> --
> >> 2.54.0
> >>
> >>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.