]> git.hungrycats.org Git - linux/commitdiff
btrfs: drain pending NOCOW writes before making a block group read-only misc-next/topics/delalloc-fixes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 06:16:28 +0000 (02:16 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:17 +0000 (17:36 -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 e6080cb47c895c45dc4a300a9dc19cb80d34c2e2..ed9c15d8ac6a2bf69d4d9fb820aa0cc65a50b365 100644 (file)
@@ -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 =
index d567ed822e55dae25243b4706f13cc1d0c4a6825..7dddd970a02dfce5f50e10bc9ca62c5fa1f6ed8f 100644 (file)
@@ -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;
index f978c6524aa03f1d66b46510fd3b1a43e30286d1..bc78aa98b48b19b727b15a301ecc600241cce2de 100644 (file)
@@ -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)
index 79d0828c51c7d7c327d0d2d59ff4a2ad95316b76..26d8aa2afa2ee47a7637ebdb31e26ce69b5bbf6d 100644 (file)
@@ -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 */
index f4b68205f621e37199799701b546e6efc5e24159..284cd2ebb8405c4e99a939ddf4634039c55203cf 100644 (file)
@@ -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;
 }