Fixed a nasty overrecycling + shrub + ckprog bug

In lfsr_mdir_compact__, we rely on shrub_.block != mdir.block to avoid
compacting shrubs multiple times. This works for the most part because
we set shrub_.block = shrub.block (the old mdir block) at the beginning
of lfsr_mdir_commit. We don't actually reset shrub_.block on a bad prog,
but in theory that was ok because we never try to compact into the same
block twice.

But this falls apart if we overrecycle the mdir!

With overrecycling, if we encounter a bad prog during a compaction and
there are no more blocks to relocate to, we try one last time to compact
into the same block (this logic is mainly for recycle overflows, where
it makes a bit more sense).

Of course, compacting into the same block breaks the above shrub_.block
!= mdir.block invariant, which causes the shrub compaction to be
skipped, uses the old shrub_.trunk (which now points to garbage), and
breaks everything.

Fortunately the solution is relatively simple: Just discard any staged
shrubs that have been committed when we relocate/overrecycle.

---

While fixing this I went ahead and renamed overcompaction ->
overrecycling. To me, overcompaction implies something _very_ different,
and I think this better describes the relationship between overrecycling
and block_recycles.

Also added test_ck_ckprogs_overrecycling to nail this down and prevent a
regression in the future. This bug _was_ caught by
test_ck_spam_fwrite_fuzz, but only after unrelated fs changes.

Adds a bit of code, but a smaller + dysfunctional filesystem is not very
useful:

           code          stack          ctx
  before: 37056           2304 (+0.0%)  636 (+0.0%)
  after:  37088 (+0.1%)   2304 (+0.0%)  636 (+0.0%)
This commit is contained in:
Christopher Haster
2025-05-22 14:51:40 -05:00
parent 1dd3b807ba
commit b613b65921
2 changed files with 233 additions and 9 deletions
+19 -6
View File
@@ -3778,6 +3778,9 @@ static int lfsr_rbyd_p_flush(lfs_t *lfs, lfsr_rbyd_t *rbyd,
static inline int lfsr_rbyd_p_push(lfs_t *lfs, lfsr_rbyd_t *rbyd,
lfsr_alt_t p[static 3],
lfsr_tag_t alt, lfsr_rid_t weight, lfs_size_t jump) {
// jump should actually be in the rbyd
LFS_ASSERT(jump < lfsr_rbyd_eoff(rbyd));
int err = lfsr_rbyd_p_flush(lfs, rbyd, p, 1);
if (err) {
return err;
@@ -8419,7 +8422,7 @@ static int lfsr_mdir_commit_(lfs_t *lfs, lfsr_mdir_t *mdir,
swap:;
// can't commit, can we compact?
bool relocated = false;
bool overcompacted = false;
bool overrecycled = false;
// check if we're within our compaction threshold
lfs_ssize_t estimate = lfsr_mdir_estimate__(lfs, mdir, start_rid, end_rid,
@@ -8447,7 +8450,7 @@ swap:;
relocate:;
// needs relocation? bad prog? ok, try allocating a new mdir
err = lfsr_mdir_alloc__(lfs, &mdir_, mdir->mid, relocated);
if (err && !(err == LFS_ERR_NOSPC && !overcompacted)) {
if (err && !(err == LFS_ERR_NOSPC && !overrecycled)) {
return err;
}
relocated = true;
@@ -8455,10 +8458,10 @@ relocate:;
// no more blocks? wear-leveling falls apart here, but we can try
// without relocating
if (err == LFS_ERR_NOSPC) {
LFS_WARN("Overcompacting mdir %"PRId32" 0x{%"PRIx32",%"PRIx32"}",
LFS_WARN("Overrecycling mdir %"PRId32" 0x{%"PRIx32",%"PRIx32"}",
lfsr_dbgmbid(lfs, mdir->mid),
mdir->rbyd.blocks[0], mdir->rbyd.blocks[1]);
overcompacted = true;
overrecycled = true;
err = lfsr_mdir_swap__(lfs, &mdir_, mdir, true);
if (err) {
@@ -8473,6 +8476,16 @@ relocate:;
}
}
// discard any committed shrubs, we need to do this explicitly
// when overrecycling
for (lfsr_omdir_t *o = lfs->omdirs; o; o = o->next) {
if (lfsr_o_isbshrub(o->flags)
&& ((lfsr_bshrub_t*)o)->shrub_.blocks[0]
== mdir_.rbyd.blocks[0]) {
((lfsr_bshrub_t*)o)->shrub_.blocks[0] = -1;
}
}
compact:;
#ifdef LFS_DBGMDIRCOMMITS
LFS_DEBUG("Compacting mdir %"PRId32" 0x{%"PRIx32",%"PRIx32"} "
@@ -8484,7 +8497,7 @@ compact:;
// don't copy over gcksum if relocating
lfsr_srid_t start_rid_ = start_rid;
if (relocated && !overcompacted) {
if (relocated && !overrecycled) {
start_rid_ = lfs_smax(start_rid_, -1);
}
@@ -8515,7 +8528,7 @@ compact:;
}
// consume gcksumdelta if relocated
if (relocated && !overcompacted) {
if (relocated && !overrecycled) {
lfs->gcksum_d ^= mdir->gcksumdelta;
}
// update mdir