preerase: Added/extended gc preerase tests, fixed a couple more bugs

This gets gc tests working with both LFS3_GC=1 and LFS3_PREERASE=1, and
adds a few more tests that should round out the necessary preerase test
coverage:

- test_gc_preerase_progress - A simple test that checks if
  LFS3_GC_PREERASE clears the LFS3_I_PREERASE flag, as well as some
  checks against emubd's erase counters to see if it actually did
  anything (erased >= cycles - preerased, erased < 1.25*cycles -
  preerased).

- test_gc_preerase_relaxed - A test with a couple different
  GC_PREERASE_COUNTs, and checks against emubd's erase counters to make
  sure they demonstrate different levels of pre-erasing (erased >=
  cycles - preerased, erased < 1.25*cycles - preerased).

- test_gc_preerase_decreasing - A test with increasing
  GC_PREERASE_COUNTs, measuring min/max/avg emubd's erase counters, and
  asserting if the avg delta is worst than ~0.75x.

  This is probably the most valuable one, if only for the extra analysis
  available when debugging.

And, just so we know these tests are working, they found a few more bugs:

- We were calling the implicitly ckpointing variant of lfs3_mdir_commit
  in lfs3_allocclaim, when the block we just allocated is still very
  much in-flight!

  An easy one-character fix (lfs3_mdir_commit -> lfs3_mdir_commit_, the
  non-ckpointing variant), but was a pain to track down. I guess the
  good news is test_gc_nospc has proven to be a very valuable test.

  Added a comment to hopefully discourage a regression.

- Found a wacky catch-22 where the block we just preerased can be
  allocated during the gbmap commit that tries to save the preerased
  ecksum.

  This is somewhat expected during normal operation, the gbmap may need
  a few allocations before the preeraser can get ahead, but we need to
  make sure not to increment the preeraser's known window if the
  preerased block is no longer in the gbmap's known window.

  Fortunately(?), our preeraser state is pretty robust to bugs like this
  due to being reset (forcing ecksum refetches) during gbmap rebuilds.
  However, preeraser state falling out-of-sync risks unnecessary
  erases/surprising latency during block allocation.

- Found a typo where we used lfs3->cfg->block_count instead of
  lfs3->block_count again... Hopefully this becomes impossible after the
  planned config rework...

---

A few other test tweaks:

- Added LFS3_F/M_REVPERTURB flags where necessary to support PREERASE.
  Previously the tests only worked with LFS3_YES_REVPERTURB=1.

- Adopt lfs3_handle_isopen over lfs3.handles == lfs3.gc.t.h. With the
  logic change to use the traversal handle to track its position in the
  open file handles, these simplified isopen checks no longer work.

- Prefer toml lists for multiple ifdefs (hey, these were at least useful
  for testing test.py's ifdef exprs).

Code changes:

                    code          stack          ctx
  before:          35260           2136          660
  after:           35260 (+0.0%)   2136 (+0.0%)  660 (+0.0%)

                    code          stack          ctx
  gbmap before:    38616           2144          776
  gbmap after:     38616 (+0.0%)   2144 (+0.0%)  776 (+0.0%)

                    code          stack          ctx
  preerase before: 39232           2168          796
  preerase after:  39280 (+0.1%)   2168 (+0.0%)  796 (+0.0%)
This commit is contained in:
Christopher Haster
2026-01-03 19:23:21 -06:00
parent 9bd44aec12
commit 29550900f2
5 changed files with 612 additions and 68 deletions
+23 -6
View File
@@ -11319,7 +11319,10 @@ static lfs3_sblock_t lfs3_allocclaim(lfs3_t *lfs3, lfs3_mdir_t *mdir,
if (lfs3_ecksum_isecksum(&ecksum_)) {
LFS3_ASSERT(lfs3_alloc_cansyncgbmap(lfs3));
// lfs3_mdir_commit implicitly commits any pending gbmap state
int err = lfs3_mdir_commit(lfs3, mdir, LFS3_RATTRS(LFS3_RATTR_NULL));
//
// note we need to not lfs3_alloc_ckpoint! the block we just
// allocated is still very much in-flight!
int err = lfs3_mdir_commit_(lfs3, mdir, LFS3_RATTRS(LFS3_RATTR_NULL));
if (err) {
return err;
}
@@ -11393,7 +11396,7 @@ static int lfs3_alloc_preerase(lfs3_t *lfs3) {
while (lfs3->gbmap.preeraser.known < lfs3->gbmap.known) {
// lookup next known block
lfs3_block_t block = (lfs3->gbmap.window + lfs3->gbmap.preeraser.known)
% lfs3->cfg->block_count;
% lfs3->block_count;
lfs3_bid_t block__;
lfs3_stag_t tag__ = lfs3_gbmap_lookupnext(lfs3, &lfs3->gbmap.b, block,
&block__, NULL, NULL);
@@ -11430,16 +11433,30 @@ static int lfs3_alloc_preerase(lfs3_t *lfs3) {
// commit into gbmap
//
// this relies on lfs3_gbmap_commit being atomic
// note this relies on lfs3_gbmap_commit being atomic
err = lfs3_gbmap_set(lfs3, &lfs3->gbmap.b, block,
LFS3_TAG_BMERASED, &ecksum);
if (err) {
return err;
}
// successful pre-erase
lfs3->gbmap.preeraser.count += 1;
lfs3->gbmap.preeraser.known += 1;
// successful pre-erase, kinda
//
// we're only actually successful if the gbmap didn't allocate
// the block we were trying to erase
// TODO can this be simplified?
if (((block+lfs3->block_count - lfs3->gbmap.window)
% lfs3->block_count)
< lfs3->gbmap.known) {
// increment preeraser
lfs3->gbmap.preeraser.count += 1;
lfs3->gbmap.preeraser.known += 1;
// if we're in the gbmap's next range, force the allocator to
// refetch ecksums
if (lfs3->gbmap.preeraser.known <= lfs3_abs(lfs3->gbmap.next)) {
lfs3->gbmap.next = 0;
}
}
return 0;
}
+1 -1
View File
@@ -383,7 +383,7 @@ static inline int32_t lfs3_smax(int32_t a, int32_t b) {
}
// Absolute value of signed numbers
static inline int32_t lfs3_abs(int32_t a) {
static inline uint32_t lfs3_abs(int32_t a) {
return (a < 0) ? -a : a;
}
+15 -2
View File
@@ -944,8 +944,21 @@ code = '''
cfg.block_count = COUNT;
lfs3_t lfs3;
// note the gbmap flag
lfs3_format(&lfs3, LFS3_F_RDWR | LFS3_F_GBMAP, &cfg) => 0;
lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0;
lfs3_format(&lfs3,
LFS3_F_RDWR
// note preerasing needs revperturb
| ((PREERASE)
? LFS3_IFDEF_PREERASE(LFS3_F_REVPERTURB, -1)
: 0)
| LFS3_F_GBMAP,
&cfg) => 0;
lfs3_mount(&lfs3,
LFS3_M_RDWR
// note preerasing needs revperturb
| ((PREERASE)
? LFS3_IFDEF_PREERASE(LFS3_M_REVPERTURB, -1)
: 0),
&cfg) => 0;
// check that we were formatted with the gbmap
struct lfs3_fsinfo fsinfo;
+572 -58
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -336,7 +336,7 @@ defines.SIZE = [
# REMOUNT=1 => remount with preerase
# REMOUNT=2 => remount without preerase
defines.REMOUNT = [0, 1, 2]
ifdef = 'LFS3_GBMAP && LFS3_REVPERTURB && LFS3_PREERASE'
ifdef = ['LFS3_GBMAP', 'LFS3_REVPERTURB', 'LFS3_PREERASE']
if = 'GBMAP'
code = '''
lfs3_t lfs3;