From: Kent Overstreet Date: Sat, 10 May 2025 03:28:01 +0000 (-0400) Subject: bcachefs: Run recovery passes asynchronously X-Git-Tag: v6.16-rc1~211^2~43 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=06977ea82b5df669c833399b4b8e2f163a8bcfbc;p=thirdparty%2Fkernel%2Flinux.git bcachefs: Run recovery passes asynchronously 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 --- diff --git a/fs/bcachefs/bcachefs.h b/fs/bcachefs/bcachefs.h index 1458f131af168..e1680b635fe13 100644 --- a/fs/bcachefs/bcachefs.h +++ b/fs/bcachefs/bcachefs.h @@ -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, diff --git a/fs/bcachefs/recovery_passes.c b/fs/bcachefs/recovery_passes.c index b931a9b465d4c..f74f14227137b 100644 --- a/fs/bcachefs/recovery_passes.c +++ b/fs/bcachefs/recovery_passes.c @@ -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); } diff --git a/fs/bcachefs/recovery_passes.h b/fs/bcachefs/recovery_passes.h index 30f896479a521..dc0d2014ff9bc 100644 --- a/fs/bcachefs/recovery_passes.h +++ b/fs/bcachefs/recovery_passes.h @@ -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); diff --git a/fs/bcachefs/recovery_passes_types.h b/fs/bcachefs/recovery_passes_types.h index deb6e0565cb9c..aa9526938cc35 100644 --- a/fs/bcachefs/recovery_passes_types.h +++ b/fs/bcachefs/recovery_passes_types.h @@ -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 */