[PATCH v2 2/2] btrfs: drain in-flight direct IO before zeroing an unaligned EOF block
From: Breno Leitao
Date: Fri Nov 06 2026 - 10:38:40 EST
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@xxxxxxxxxxxxxxx
Reported-by: Qu Wenruo <quwenruo.btrfs@xxxxxxx>
Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
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