From: Breno Leitao <leitao@debian.org>
To: David Sterba <dsterba@suse.com>, Chris Mason <mason@kernel.org>
Cc: Qu Wenruo <wqu@suse.com>, Filipe Manana <fdmanana@kernel.org>,
Boris Burkov <boris@bur.io>, Atish Patra <atish.patra@linux.dev>,
linux-btrfs@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
Breno Leitao <leitao@debian.org>,
kernel-team@meta.com, stable@vger.kernel.org,
Qu Wenruo <quwenruo.btrfs@gmx.com>
Subject: [PATCH v2 2/2] btrfs: drain in-flight direct IO before zeroing an unaligned EOF block
Date: Fri, 06 Nov 2026 07:21:11 -0800 [thread overview]
Message-ID: <20261106-btrfs_page_cache-v2-2-f57575d0071d@debian.org> (raw)
In-Reply-To: <20261106-btrfs_page_cache-v2-0-f57575d0071d@debian.org>
A write that starts past an unaligned i_size first zeroes the rest of
the block at i_size: btrfs_write_check() calls btrfs_cont_expand(),
whose btrfs_truncate_block() reads the folio, zeroes it past i_size and
marks that block dirty.
Reading the folio waits for the ordered extents of direct writes to the
other blocks of the folio, but not for their iomap completion, which
runs separately from the ordered extent completion and is where the
page cache gets invalidated:
1) Task A has an async direct write in flight to block 0 of a folio.
2) A fallen back write or a truncate left i_size in the middle of block
1 of the same folio. Task B issues an O_DIRECT write past i_size, and
btrfs_truncate_block() waits for the ordered extent of Task A.
3) Task A's write completes and its ordered extent finishes, so Task B
zeroes the rest of block 1 and dirties the folio. When iomap then
completes Task A's write, kiocb_invalidate_post_direct_write() finds
the folio dirty and records -EIO in the mapping, so the next fsync()
fails.
btrfs_direct_write() calls btrfs_write_check() before it decides to fall
back to buffered IO, so this hits direct writes past EOF as well as the
unaligned ones that fall back: for those, the inode_dio_wait() in
btrfs_buffered_write() runs after the folio is dirty.
Wait for in-flight direct IO before zeroing, as XFS does in
xfs_file_write_zero_eof(). Unlike waiting for the ordered extents,
inode_dio_wait() covers the invalidation, as iomap_dio_complete() calls
inode_dio_end() after it.
Only an unaligned i_size needs the wait, as btrfs_truncate_block()
dirties nothing otherwise. Buffered and encoded writes past such an
i_size wait as well, since they share btrfs_write_check().
A reproducer keeps 4K AIO O_DIRECT writes in flight to the first 15
blocks of a folio of a NOCOW file. Another task truncates the file to
end inside the last block of that folio, then writes past EOF.
On a 64K page VM it got 56 fdatasync() EIOs in 60 seconds before this
patch, 38 when the write past EOF is 512 bytes and falls back, and none
in two runs with this patch.
Qu Wenruo pointed this path out, from a Sashiko review.
Fixes: 95ea0486b20e ("btrfs: allow read-write for 4K sectorsize on 64K page size systems")
Cc: stable@vger.kernel.org
Reported-by: Qu Wenruo <quwenruo.btrfs@gmx.com>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
fs/btrfs/file.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 2017a96814d857..15802db7b664b8 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1063,6 +1063,13 @@ int btrfs_write_check(struct kiocb *iocb, size_t count)
/* Expand hole size to cover write data, preventing empty gap */
loff_t end_pos = round_up(pos + count, fs_info->sectorsize);
+ /*
+ * Zeroing the block at an unaligned EOF dirties its folio, so
+ * drain in-flight direct IO first, see btrfs_buffered_write().
+ */
+ if (!IS_ALIGNED(oldsize, fs_info->sectorsize))
+ inode_dio_wait(inode);
+
ret = btrfs_cont_expand(BTRFS_I(inode), oldsize, end_pos);
if (ret)
return ret;
--
2.53.0-Meta
prev parent reply other threads:[~2026-11-06 15:26 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 15:21 [PATCH v2 0/2] btrfs: drain in-flight direct IO before direct writes dirty the page cache Breno Leitao
2026-11-06 15:21 ` [PATCH v2 1/2] btrfs: drain in-flight direct IO before buffered write fallback Breno Leitao
2026-11-06 15:21 ` Breno Leitao [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=20261106-btrfs_page_cache-v2-2-f57575d0071d@debian.org \
--to=leitao@debian.org \
--cc=atish.patra@linux.dev \
--cc=boris@bur.io \
--cc=dsterba@suse.com \
--cc=fdmanana@kernel.org \
--cc=kernel-team@meta.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-btrfs@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mason@kernel.org \
--cc=quwenruo.btrfs@gmx.com \
--cc=stable@vger.kernel.org \
--cc=wqu@suse.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
all inboxes | Powered by JetHome®