From: Zygo Blaxell Date: Sat, 12 Sep 2026 06:16:28 +0000 (-0400) Subject: btrfs: drain pending NOCOW writes before making a block group read-only X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Fmisc-next%2Ftopics%2Fdelalloc-fixes;p=linux btrfs: drain pending NOCOW writes before making a block group read-only A buffered write to a nodatacow or preallocated range decides at write() time, in btrfs_check_nocow_lock(), that it will be written in place, and records that with EXTENT_NORESERVE: no data space is reserved for it. The block group's nocow_writers count, which relocation and scrub wait for, is only taken at writeback, in run_delalloc_nocow(). Between the two nothing stops the group from going read-only -- scrub, balance and zoned reclaim all call btrfs_inc_block_group_ro() without flushing anything -- and when the writeback then finds ->ro set it falls back to COW: fallback_to_cow() charges the data space without any admission check, and on a full filesystem cow_file_range() fails and the pages are dropped, the error surfacing only at fsync or close. Snapshots and reflinks flush the range before changing its sharing, so this window is the read-only transition's alone. Drain the group first. btrfs_extent_readonly() now refuses NOCOW into a group that is being made read-only and notes, per group, that a write() has decided to NOCOW into it; btrfs_check_nocow_lock() holds an fs-wide in-flight count until btrfs_check_nocow_unlock(), i.e. until the pages are dirtied. btrfs_inc_block_group_ro() blocks new decisions, waits for the in-flight ones, and -- only if a NOCOW decision has landed on the group since it was last drained -- flushes delalloc and waits for the group's ordered extents, so every pending range is written in place while the group is still writable, before the transaction is joined and the group flipped. The block is lifted if the transition fails and when the group returns to read-write. Assisted-by: Claude:claude-opus-4-8 --- diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index e6080cb47c895..ed9c15d8ac6a2 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -3079,6 +3079,53 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran * ensure we still have some free space after marking this * block group RO. */ +/* + * A buffered NOCOW write decides to write in place at write() time + * (btrfs_check_nocow_lock()) but takes the group's nocow_writers only at + * writeback. If the group goes read-only in between -- scrub, balance, + * zoned reclaim -- the writeback finds ->ro, falls back to COW with a data + * reservation that never went through admission (fallback_to_cow()), and + * on a full filesystem cow_file_range() fails and the pages are dropped. + * + * Drain the group before making it read-only: refuse new NOCOW decisions + * into it, wait for the write() calls that already decided to finish + * dirtying their pages, then flush delalloc and wait for the group's + * ordered extents so every pending NOCOW range lands in place while the + * group is still writable. The flush is skipped when no write() has + * decided to NOCOW into the group since it was last drained. Must not be + * called with a transaction handle held: the flush completes ordered + * extents, which join transactions. + */ +static int btrfs_bg_drain_nocow_writes(struct btrfs_block_group *bg) +{ + struct btrfs_fs_info *fs_info = bg->fs_info; + bool pending; + int ret; + + spin_lock(&bg->lock); + bg->nocow_blocked = true; + pending = bg->nocow_pending; + bg->nocow_pending = false; + spin_unlock(&bg->lock); + wait_var_event(&fs_info->nocow_write_inflight, + !atomic_read(&fs_info->nocow_write_inflight)); + if (!pending) + return 0; + ret = btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); + if (ret < 0) + return ret; + btrfs_wait_ordered_roots(fs_info, U64_MAX, bg); + btrfs_wait_nocow_writers(bg); + return 0; +} + +static void btrfs_bg_unblock_nocow(struct btrfs_block_group *bg) +{ + spin_lock(&bg->lock); + bg->nocow_blocked = false; + spin_unlock(&bg->lock); +} + int btrfs_inc_block_group_ro(struct btrfs_block_group *cache, bool do_chunk_alloc) { @@ -3108,6 +3155,14 @@ int btrfs_inc_block_group_ro(struct btrfs_block_group *cache, return ret; } + if (cache->flags & BTRFS_BLOCK_GROUP_DATA) { + ret = btrfs_bg_drain_nocow_writes(cache); + if (ret) { + btrfs_bg_unblock_nocow(cache); + return ret; + } + } + do { trans = btrfs_join_transaction(root); if (IS_ERR(trans)) @@ -3195,6 +3250,8 @@ unlock_out: mutex_unlock(&fs_info->ro_block_group_mutex); btrfs_end_transaction(trans); + if (ret) + btrfs_bg_unblock_nocow(cache); return ret; } @@ -3207,6 +3264,7 @@ void btrfs_dec_block_group_ro(struct btrfs_block_group *cache) spin_lock(&sinfo->lock); spin_lock(&cache->lock); if (!--cache->ro) { + cache->nocow_blocked = false; if (btrfs_is_zoned(cache->fs_info)) { /* Migrate zone_unusable bytes back */ cache->zone_unusable = diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h index d567ed822e55d..7dddd970a02df 100644 --- a/fs/btrfs/block-group.h +++ b/fs/btrfs/block-group.h @@ -239,6 +239,15 @@ struct btrfs_block_group { * writes through direct IO. */ atomic_t nocow_writers; + /* + * Both under *lock*. nocow_blocked: the group is being made read-only + * (or is), so no write() may decide to NOCOW into it any more. + * nocow_pending: some write() has decided to NOCOW into it since the + * last read-only transition drained it, so pending NOCOW delalloc may + * point here. See btrfs_bg_drain_nocow_writes(). + */ + bool nocow_blocked; + bool nocow_pending; /* Lock for free space tree operations. */ struct mutex free_space_lock; diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index f978c6524aa03..bc78aa98b48b1 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -971,6 +971,14 @@ int btrfs_check_nocow_lock(struct btrfs_inode *inode, loff_t pos, if (!btrfs_drew_try_write_lock(&root->snapshot_lock)) return -EAGAIN; + /* + * Held until btrfs_check_nocow_unlock(), i.e. until the pages are + * dirtied: a block group going read-only waits for it, so it cannot + * turn a range we decided to NOCOW into a COW fallback at writeback. + * Taken before the decision so a group's read-only transition that + * lands during it sees us either way. + */ + atomic_inc(&fs_info->nocow_write_inflight); lockstart = round_down(pos, fs_info->sectorsize); lockend = round_up(pos + *write_bytes, @@ -979,7 +987,7 @@ int btrfs_check_nocow_lock(struct btrfs_inode *inode, loff_t pos, if (nowait) { if (!btrfs_try_lock_ordered_range(inode, lockstart, lockend, &cached_state)) { - btrfs_drew_write_unlock(&root->snapshot_lock); + btrfs_check_nocow_unlock(inode); return -EAGAIN; } } else { @@ -999,7 +1007,7 @@ int btrfs_check_nocow_lock(struct btrfs_inode *inode, loff_t pos, * snapshot lock. */ if (cur_offset == lockstart) - btrfs_drew_write_unlock(&root->snapshot_lock); + btrfs_check_nocow_unlock(inode); break; } cur_offset += num_bytes; @@ -1021,7 +1029,11 @@ int btrfs_check_nocow_lock(struct btrfs_inode *inode, loff_t pos, void btrfs_check_nocow_unlock(struct btrfs_inode *inode) { + struct btrfs_fs_info *fs_info = inode->root->fs_info; + btrfs_drew_write_unlock(&inode->root->snapshot_lock); + if (atomic_dec_and_test(&fs_info->nocow_write_inflight)) + wake_up_var(&fs_info->nocow_write_inflight); } int btrfs_write_check(struct kiocb *iocb, size_t count) diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 79d0828c51c7d..26d8aa2afa2ee 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -669,6 +669,14 @@ struct btrfs_fs_info { atomic_t nr_delayed_iputs; wait_queue_head_t delayed_iputs_wait; + /* + * write() calls between deciding to NOCOW (btrfs_check_nocow_lock()) + * and having dirtied their pages (btrfs_check_nocow_unlock()). A + * block group going read-only waits for them before flushing the + * pending NOCOW ranges; see btrfs_bg_drain_nocow_writes(). + */ + atomic_t nocow_write_inflight; + atomic64_t tree_mod_seq; /* This protects tree_mod_log and tree_mod_seq_list */ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index f4b68205f621e..284cd2ebb8405 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7372,10 +7372,20 @@ static bool btrfs_extent_readonly(struct btrfs_fs_info *fs_info, u64 bytenr) bool readonly = false; block_group = btrfs_lookup_block_group(fs_info, bytenr); - if (!block_group || block_group->ro) + if (!block_group) + return true; + spin_lock(&block_group->lock); + if (block_group->ro || block_group->nocow_blocked) { readonly = true; - if (block_group) - btrfs_put_block_group(block_group); + } else { + /* + * The caller may now dirty a NOCOW range against this group; + * a later read-only transition has to flush it first. + */ + block_group->nocow_pending = true; + } + spin_unlock(&block_group->lock); + btrfs_put_block_group(block_group); return readonly; }