]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
bcachefs: Run recovery passes asynchronously
authorKent Overstreet <kent.overstreet@linux.dev>
Sat, 10 May 2025 03:28:01 +0000 (23:28 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Thu, 22 May 2025 00:15:04 +0000 (20:15 -0400)
When we request a recovery pass to be run online, i.e. not during
recovery, if it's an online pass it'll now be run in the background,
instead of waiting for the next mount.

To avoid situations where recovery passes are running continuously, this
also includes ratelimiting: if the RUN_RECOVERY_PASS_ratelimit flag is
passed, the pass may be deferred until later - depending on the runtime
and last run stats in the recovery_passes superblock section.

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

index 1458f131af168e9552346b4ed183fc11e8ad52d8..e1680b635fe13158dc5dbd1ca43fc3d857983a16 100644 (file)
@@ -760,7 +760,8 @@ struct btree_trans_buf {
        x(snapshot_delete_pagecache)                                    \
        x(sysfs)                                                        \
        x(btree_write_buffer)                                           \
-       x(btree_node_scrub)
+       x(btree_node_scrub)                                             \
+       x(async_recovery_passes)
 
 enum bch_write_ref {
 #define x(n) BCH_WRITE_REF_##n,
index b931a9b465d4c97dd5896e3ddf0281181d8bb83a..f74f14227137b0e8e75f9f9d95d925ca7fcc8830 100644 (file)
@@ -138,6 +138,30 @@ out:
        mutex_unlock(&c->sb_lock);
 }
 
+static bool bch2_recovery_pass_want_ratelimit(struct bch_fs *c, enum bch_recovery_pass pass)
+{
+       enum bch_recovery_pass_stable stable = bch2_recovery_pass_to_stable(pass);
+       bool ret = false;
+
+       lockdep_assert_held(&c->sb_lock);
+
+       struct bch_sb_field_recovery_passes *r =
+               bch2_sb_field_get(c->disk_sb.sb, recovery_passes);
+
+       if (stable < recovery_passes_nr_entries(r)) {
+               struct recovery_pass_entry *i = r->start + stable;
+
+               /*
+                * Ratelimit if the last runtime was more than 1% of the time
+                * since we last ran
+                */
+               ret = (u64) le32_to_cpu(i->last_runtime) * 100 >
+                       ktime_get_real_seconds() - le64_to_cpu(i->last_run);
+       }
+
+       return ret;
+}
+
 const struct bch_sb_field_ops bch_sb_field_ops_recovery_passes = {
        .validate       = bch2_sb_recovery_passes_validate,
        .to_text        = bch2_sb_recovery_passes_to_text
@@ -218,13 +242,33 @@ u64 bch2_fsck_recovery_passes(void)
        return bch2_recovery_passes_match(PASS_FSCK);
 }
 
+static void bch2_run_async_recovery_passes(struct bch_fs *c)
+{
+       if (!down_trylock(&c->recovery.run_lock))
+               return;
+
+       if (!enumerated_ref_tryget(&c->writes, BCH_WRITE_REF_async_recovery_passes))
+               goto unlock;
+
+       if (queue_work(system_long_wq, &c->recovery.work))
+               return;
+
+       enumerated_ref_put(&c->writes, BCH_WRITE_REF_async_recovery_passes);
+unlock:
+       up(&c->recovery.run_lock);
+}
+
 static bool recovery_pass_needs_set(struct bch_fs *c,
                                    enum bch_recovery_pass pass,
-                                   enum bch_run_recovery_pass_flags flags)
+                                   enum bch_run_recovery_pass_flags *flags)
 {
        struct bch_fs_recovery *r = &c->recovery;
        bool in_recovery = test_bit(BCH_FS_in_recovery, &c->flags);
-       bool persistent = !in_recovery || !(flags & RUN_RECOVERY_PASS_nopersistent);
+       bool persistent = !in_recovery || !(*flags & RUN_RECOVERY_PASS_nopersistent);
+
+       if ((*flags & RUN_RECOVERY_PASS_ratelimit) &&
+           !bch2_recovery_pass_want_ratelimit(c, pass))
+               *flags &= ~RUN_RECOVERY_PASS_ratelimit;
 
        /*
         * If RUN_RECOVERY_PASS_nopersistent is set, we don't want to do
@@ -236,9 +280,16 @@ static bool recovery_pass_needs_set(struct bch_fs *c,
         * it should run again even if it's already run:
         */
 
-       return persistent
-               ? !(c->sb.recovery_passes_required & BIT_ULL(pass))
-               : !((r->passes_to_run|r->passes_complete) & BIT_ULL(pass));
+       if (persistent
+           ? !(c->sb.recovery_passes_required & BIT_ULL(pass))
+           : !((r->passes_to_run|r->passes_complete) & BIT_ULL(pass)))
+               return true;
+
+       if (!(*flags & RUN_RECOVERY_PASS_ratelimit) &&
+           (r->passes_ratelimiting & BIT_ULL(pass)))
+               return true;
+
+       return false;
 }
 
 /*
@@ -260,15 +311,14 @@ int __bch2_run_explicit_recovery_pass(struct bch_fs *c,
        unsigned long lockflags;
        spin_lock_irqsave(&r->lock, lockflags);
 
-       if (!recovery_pass_needs_set(c, pass, flags))
+       if (!recovery_pass_needs_set(c, pass, &flags))
                goto out;
 
        bool in_recovery = test_bit(BCH_FS_in_recovery, &c->flags);
        bool rewind = in_recovery && r->curr_pass > pass;
+       bool ratelimit = flags & RUN_RECOVERY_PASS_ratelimit;
 
-       if ((flags & RUN_RECOVERY_PASS_nopersistent) && in_recovery) {
-               r->passes_to_run |= BIT_ULL(pass);
-       } else {
+       if (!(in_recovery && (flags & RUN_RECOVERY_PASS_nopersistent))) {
                struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
                __set_bit_le64(bch2_recovery_pass_to_stable(pass), ext->recovery_passes_required);
        }
@@ -281,18 +331,32 @@ int __bch2_run_explicit_recovery_pass(struct bch_fs *c,
                goto out;
        }
 
-       prt_printf(out, "running recovery pass %s (%u), currently at %s (%u)%s\n",
-                  bch2_recovery_passes[pass], pass,
-                  bch2_recovery_passes[r->curr_pass], r->curr_pass,
-                  rewind ? " - rewinding" : "");
+       if (ratelimit)
+               r->passes_ratelimiting |= BIT_ULL(pass);
+       else
+               r->passes_ratelimiting &= ~BIT_ULL(pass);
+
+       if (in_recovery && !ratelimit) {
+               prt_printf(out, "running recovery pass %s (%u), currently at %s (%u)%s\n",
+                          bch2_recovery_passes[pass], pass,
+                          bch2_recovery_passes[r->curr_pass], r->curr_pass,
+                          rewind ? " - rewinding" : "");
 
-       if (test_bit(BCH_FS_in_recovery, &c->flags))
                r->passes_to_run |= BIT_ULL(pass);
 
-       if (rewind) {
-               r->next_pass = pass;
-               r->passes_complete &= (1ULL << pass) >> 1;
-               ret = -BCH_ERR_restart_recovery;
+               if (rewind) {
+                       r->next_pass = pass;
+                       r->passes_complete &= (1ULL << pass) >> 1;
+                       ret = -BCH_ERR_restart_recovery;
+               }
+       } else {
+               prt_printf(out, "scheduling recovery pass %s (%u)%s\n",
+                          bch2_recovery_passes[pass], pass,
+                          ratelimit ? " - ratelimiting" : "");
+
+               struct recovery_pass_fn *p = recovery_pass_fns + pass;
+               if (p->when & PASS_ONLINE)
+                       bch2_run_async_recovery_passes(c);
        }
 out:
        spin_unlock_irqrestore(&r->lock, lockflags);
@@ -305,20 +369,24 @@ int bch2_run_explicit_recovery_pass(struct bch_fs *c,
                                    enum bch_recovery_pass pass,
                                    enum bch_run_recovery_pass_flags flags)
 {
-       if (!recovery_pass_needs_set(c, pass, flags))
-               return 0;
+       int ret = 0;
 
-       mutex_lock(&c->sb_lock);
-       int ret = __bch2_run_explicit_recovery_pass(c, out, pass, flags);
-       bch2_write_super(c);
-       mutex_unlock(&c->sb_lock);
+       scoped_guard(mutex, &c->sb_lock) {
+               if (!recovery_pass_needs_set(c, pass, &flags))
+                       return 0;
+
+               ret = __bch2_run_explicit_recovery_pass(c, out, pass, flags);
+               bch2_write_super(c);
+       }
 
        return ret;
 }
 
 int bch2_run_print_explicit_recovery_pass(struct bch_fs *c, enum bch_recovery_pass pass)
 {
-       if (!recovery_pass_needs_set(c, pass, RUN_RECOVERY_PASS_nopersistent))
+       enum bch_run_recovery_pass_flags flags = RUN_RECOVERY_PASS_nopersistent;
+
+       if (!recovery_pass_needs_set(c, pass, &flags))
                return 0;
 
        struct printbuf buf = PRINTBUF;
@@ -430,6 +498,19 @@ static int __bch2_run_recovery_passes(struct bch_fs *c, u64 orig_passes_to_run,
        return ret;
 }
 
+static void bch2_async_recovery_passes_work(struct work_struct *work)
+{
+       struct bch_fs *c = container_of(work, struct bch_fs, recovery.work);
+       struct bch_fs_recovery *r = &c->recovery;
+
+       __bch2_run_recovery_passes(c,
+               c->sb.recovery_passes_required & ~r->passes_ratelimiting,
+               true);
+
+       up(&r->run_lock);
+       enumerated_ref_put(&c->writes, BCH_WRITE_REF_async_recovery_passes);
+}
+
 int bch2_run_online_recovery_passes(struct bch_fs *c, u64 passes)
 {
        return __bch2_run_recovery_passes(c, c->sb.recovery_passes_required|passes, true);
@@ -488,4 +569,6 @@ void bch2_fs_recovery_passes_init(struct bch_fs *c)
 {
        spin_lock_init(&c->recovery.lock);
        sema_init(&c->recovery.run_lock, 1);
+
+       INIT_WORK(&c->recovery.work, bch2_async_recovery_passes_work);
 }
index 30f896479a521dc7f6cb38b16b4f3526b4c0e569..dc0d2014ff9bc598a3aab00396d11544363fb707 100644 (file)
@@ -12,6 +12,7 @@ u64 bch2_fsck_recovery_passes(void);
 
 enum bch_run_recovery_pass_flags {
        RUN_RECOVERY_PASS_nopersistent  = BIT(0),
+       RUN_RECOVERY_PASS_ratelimit     = BIT(1),
 };
 
 int bch2_run_print_explicit_recovery_pass(struct bch_fs *, enum bch_recovery_pass);
index deb6e0565cb9c9623e64f203c21d60a34b579035..aa9526938cc35d94428226103c46d5a9e19c991b 100644 (file)
@@ -18,8 +18,10 @@ struct bch_fs_recovery {
        /* bitmask of recovery passes that we actually ran */
        u64                     passes_complete;
        u64                     passes_failing;
+       u64                     passes_ratelimiting;
        spinlock_t              lock;
        struct semaphore        run_lock;
+       struct work_struct      work;
 };
 
 #endif /* _BCACHEFS_RECOVERY_PASSES_TYPES_H */