diff --git a/fs/bcachefs/bcachefs.h b/fs/bcachefs/bcachefs.h index 1458f131af16..e1680b635fe1 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 b931a9b465d4..f74f14227137 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 30f896479a52..dc0d2014ff9b 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 deb6e0565cb9..aa9526938cc3 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 */