bcachefs: BCH_RECOVERY_PASS_NO_RATELIMIT
authorKent Overstreet <kent.overstreet@linux.dev>
Sat, 31 May 2025 17:01:44 +0000 (13:01 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Mon, 2 Jun 2025 16:16:36 +0000 (12:16 -0400)
Add a superblock flag to temporarily disable ratelimiting for a recovery
pass.

This will be used to make check_key_has_snapshot safer: we don't want to
delete a key for a missing snapshot unless we know that the snapshots
and subvolumes btrees are consistent, i.e. check_snapshots and
check_subvols have run recently.

Changing those btrees - creating/deleting a subvolume or snapshot - will
set the "disable ratelimit" flag, i.e. ensuring that those passes run if
check_key_has_snapshot discovers an error.

We're only disabling ratelimiting in the snapshot/subvol delete paths,
we're not so concerned about the create paths.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/recovery_passes.c
fs/bcachefs/recovery_passes.h
fs/bcachefs/recovery_passes_format.h
fs/bcachefs/snapshot.c
fs/bcachefs/subvolume.c

index 9da43452fb300a650acbcc9f1c916b157f0b57ec..605588e33fb3d0ba4f8b445a24551ab20112cfeb 100644 (file)
@@ -103,20 +103,20 @@ static void bch2_sb_recovery_passes_to_text(struct printbuf *out,
                prt_tab(out);
 
                bch2_pr_time_units(out, le32_to_cpu(i->last_runtime) * NSEC_PER_SEC);
+
+               if (BCH_RECOVERY_PASS_NO_RATELIMIT(i))
+                       prt_str(out, " (no ratelimit)");
+
                prt_newline(out);
        }
 }
 
-static void bch2_sb_recovery_pass_complete(struct bch_fs *c,
-                                          enum bch_recovery_pass pass,
-                                          s64 start_time)
+static struct recovery_pass_entry *bch2_sb_recovery_pass_entry(struct bch_fs *c,
+                                                              enum bch_recovery_pass pass)
 {
        enum bch_recovery_pass_stable stable = bch2_recovery_pass_to_stable(pass);
-       s64 end_time = ktime_get_real_seconds();
 
-       mutex_lock(&c->sb_lock);
-       struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
-       __clear_bit_le64(stable, ext->recovery_passes_required);
+       lockdep_assert_held(&c->sb_lock);
 
        struct bch_sb_field_recovery_passes *r =
                bch2_sb_field_get(c->disk_sb.sb, recovery_passes);
@@ -127,15 +127,43 @@ static void bch2_sb_recovery_pass_complete(struct bch_fs *c,
                r = bch2_sb_field_resize(&c->disk_sb, recovery_passes, u64s);
                if (!r) {
                        bch_err(c, "error creating recovery_passes sb section");
-                       goto out;
+                       return NULL;
                }
        }
 
-       r->start[stable].last_run       = cpu_to_le64(end_time);
-       r->start[stable].last_runtime   = cpu_to_le32(max(0, end_time - start_time));
-out:
+       return r->start + stable;
+}
+
+static void bch2_sb_recovery_pass_complete(struct bch_fs *c,
+                                          enum bch_recovery_pass pass,
+                                          s64 start_time)
+{
+       guard(mutex)(&c->sb_lock);
+       struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
+       __clear_bit_le64(bch2_recovery_pass_to_stable(pass),
+                        ext->recovery_passes_required);
+
+       struct recovery_pass_entry *e = bch2_sb_recovery_pass_entry(c, pass);
+       if (e) {
+               s64 end_time    = ktime_get_real_seconds();
+               e->last_run     = cpu_to_le64(end_time);
+               e->last_runtime = cpu_to_le32(max(0, end_time - start_time));
+               SET_BCH_RECOVERY_PASS_NO_RATELIMIT(e, false);
+       }
+
        bch2_write_super(c);
-       mutex_unlock(&c->sb_lock);
+}
+
+void bch2_recovery_pass_set_no_ratelimit(struct bch_fs *c,
+                                        enum bch_recovery_pass pass)
+{
+       guard(mutex)(&c->sb_lock);
+
+       struct recovery_pass_entry *e = bch2_sb_recovery_pass_entry(c, pass);
+       if (e && !BCH_RECOVERY_PASS_NO_RATELIMIT(e)) {
+               SET_BCH_RECOVERY_PASS_NO_RATELIMIT(e, false);
+               bch2_write_super(c);
+       }
 }
 
 static bool bch2_recovery_pass_want_ratelimit(struct bch_fs *c, enum bch_recovery_pass pass)
@@ -157,6 +185,9 @@ static bool bch2_recovery_pass_want_ratelimit(struct bch_fs *c, enum bch_recover
                 */
                ret = (u64) le32_to_cpu(i->last_runtime) * 100 >
                        ktime_get_real_seconds() - le64_to_cpu(i->last_run);
+
+               if (BCH_RECOVERY_PASS_NO_RATELIMIT(i))
+                       ret = false;
        }
 
        return ret;
index a97a462b5e1133decdfe14241165a72fe9f5c22c..260571c7105edc35b6a37bc32dc8e26c3a5db7f7 100644 (file)
@@ -10,6 +10,8 @@ u64 bch2_recovery_passes_from_stable(u64 v);
 
 u64 bch2_fsck_recovery_passes(void);
 
+void bch2_recovery_pass_set_no_ratelimit(struct bch_fs *, enum bch_recovery_pass);
+
 enum bch_run_recovery_pass_flags {
        RUN_RECOVERY_PASS_nopersistent  = BIT(0),
        RUN_RECOVERY_PASS_ratelimit     = BIT(1),
index c434eafbca196f58f60aa61d55d40ac66bc9201d..b63c20558d3d42c48973dc095b1039ade3a11ad5 100644 (file)
@@ -87,6 +87,8 @@ struct recovery_pass_entry {
        __le32                  flags;
 };
 
+LE32_BITMASK(BCH_RECOVERY_PASS_NO_RATELIMIT,   struct recovery_pass_entry, flags, 0, 1)
+
 struct bch_sb_field_recovery_passes {
        struct bch_sb_field     field;
        struct recovery_pass_entry start[];
index 24818d29982d51f61bcfff10ec1dc33d2552f3b2..0649bdd5e4d23529d9e7ca66e6e33f29b3d22fb6 100644 (file)
@@ -1878,6 +1878,8 @@ err:
        d->running = false;
        mutex_unlock(&d->progress_lock);
        bch2_trans_put(trans);
+
+       bch2_recovery_pass_set_no_ratelimit(c, BCH_RECOVERY_PASS_check_snapshots);
 out_unlock:
        mutex_unlock(&d->lock);
        if (!bch2_err_matches(ret, EROFS))
index e22837396bd9c321afbcfd05598c46551c8a1c26..77ba50df2ff4906168d0cf9f90d9ef12d0b1cfde 100644 (file)
@@ -482,9 +482,12 @@ err:
 
 static int bch2_subvolume_delete(struct btree_trans *trans, u32 subvolid)
 {
-       return bch2_subvolumes_reparent(trans, subvolid) ?:
+       int ret = bch2_subvolumes_reparent(trans, subvolid) ?:
                commit_do(trans, NULL, NULL, BCH_TRANS_COMMIT_no_enospc,
                          __bch2_subvolume_delete(trans, subvolid));
+
+       bch2_recovery_pass_set_no_ratelimit(trans->c, BCH_RECOVERY_PASS_check_subvols);
+       return ret;
 }
 
 static void bch2_subvolume_wait_for_pagecache_and_delete(struct work_struct *work)