]> git.hungrycats.org Git - linux/commitdiff
btrfs: drain pending NOCOW writes before making a block group read-only 6.18/topics/delalloc-fixes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 06:13:30 +0000 (02:13 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:39:58 +0000 (17:39 -0400)
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
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/file.c
fs/btrfs/fs.h
fs/btrfs/inode.c

index e9ee10bee2ece67c413696c1c3eea3d42015cff3..12ab34c571b5d639401aa8757e81ad84caabc9f8 100644 (file)
@@ -2979,6 +2979,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)
 {
@@ -3003,6 +3050,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))
@@ -3090,6 +3145,8 @@ unlock_out:
        mutex_unlock(&fs_info->ro_block_group_mutex);
 
        btrfs_end_transaction(trans);
+       if (ret)
+               btrfs_bg_unblock_nocow(cache);
        return ret;
 }
 
@@ -3103,6 +3160,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 =
index 9172104a5889ece6ff839158c22714186ae730a9..3c37049ad5b243ab505abd86d5ff74e14d63af13 100644 (file)
@@ -242,6 +242,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;
index a15062f976ba37fcc68b7a535a04e42677d93557..0f8dcfbc6b2dc66e8610ec7fcd23579943ceb618 100644 (file)
@@ -977,6 +977,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,
@@ -985,7 +993,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 {
@@ -1005,7 +1013,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;
@@ -1027,7 +1035,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)
index eccc61463947be0cedf79bcc343053e7e5fb35f0..02850d6eea52f146da5d273c6008778d6058eaa3 100644 (file)
@@ -615,6 +615,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 */
index cca9840bee8966e9c4a0bea9dbf4b98001dbfe3f..55c200a05e2751b0557ab64f95250767595940c6 100644 (file)
@@ -7307,10 +7307,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;
 }