Commit Graph

8 Commits

Author SHA1 Message Date
Christopher Haster 5b72973f8f t: Filled out rest of test_traversal
There can always be more tests, but I think these give a nice set of
coverage over corner-cases in our traversal clobbering scheme.

These did find a couple bugs:

- If we clobber an inlined mroot, we need to adjust the mid by two
  mdirs, but only if there is no mtree/mdirs.

  To avoid this and other mid-related headaches, we just provide the new
  mid in lfsr_mdir_commit, since we always know it here.

- lfsr_mdir_commit compares mdirs by mptr, which means we need to
  clobber traversal's mdir's mptrs or else lfsr_mdir_commit will clobber
  already-clobbered traversals.

  There may be a better way to solve this, but it will probably get into
  the weeds with how lfsr_mdir_commit relies on mids vs mptrs...

Code changes:

           code          stack
  before: 34570           2624
  after:  34566 (-0.0%)   2624 (+0.0%)

Now that the dust has settled and we sort of know what the traversal
implementation will look like, we can look at the before and after to
get a rough idea of how much the traversal API actually costs:

                          code          stack
  no-traversal (before): 33886           2560
  yes-traversal (after): 34566 (+2.0%)   2624 (+2.5%)

Note this still includes the annoying lfsr_btree_traverse inlining stack
cost, which isn't really the traversal API's fault and may be avoidable
in the future.
2024-06-20 13:13:58 -05:00
Christopher Haster 51357bc925 Readded lfsr_fs_mkconsistent
See comments/previous commits. lfsr_fs_mkconsistent allows running
internal consistency operations without any other filesystem changes.

Implementation-wize, this just calls lfsr_fs_preparemutation which we
already need to, uh, prepare for mutation. Though it may do some
additional work in the future, such as setting compat flags, version
numbers, etc.

Added mkconsistent permutations to what seems like the relevant tests:

- test_forphans - easy for lfsr_fs_mkconsistent to accidentally delete
  orphans/zombies.

- test_powerloss - heavy fuzz tests over powerloss-related consistency
  operations, though this does multiply every permutation by ~2x...

Code cost minimal. I guess this is what it costs to make an internal
function non-static:

           code          stack
  before: 33634           2592
  after:  33642 (+0.0%)   2592 (+0.0%)
2024-06-08 16:08:59 -05:00
Christopher Haster 2402e108db Reduced test_powerloss OPS from 1024 -> 256
Now that we are testing more powerloss behaviors, test_powerloss is the
longest running test suite by a decent margin:

Before:

  $ ./scripts/summary.py test.csv -bsuite -ftime -Stime
  ... snip ...
  test_rbyd                  578.6
  test_fwrite                984.5
  test_badblocks            1341.5
  test_exhaustion           1648.3
  test_powerloss            2192.3 <--
  TOTAL                     7378.6

  $ ./scripts/summary.py test.csv -bcase -ftime -Stime
  ... snip ...
  test_fwrite_fuzz_aligned                           247.2
  test_exhaustion_file_fuzz                          287.7
  test_exhaustion_dir_fuzz                           307.2
  test_exhaustion_orphanzombie_fuzz                  389.1
  test_exhaustion_orphanzombiedir_fuzz               531.5
  test_powerloss_file_pl_fuzz                        787.7 <--
  test_badblocks_single_dir_many                     840.2
  test_powerloss_filedir_pl_fuzz                    1366.7 <--
  TOTAL                                             7378.6

But testing more things is better than testing the same thing more.

Worst case you can always manually override OPS, -DOPS=1024, if you have
CI cycles to spare. Though note with our linear powerloss heuristic,
the tail end of long running tests also recieves fewer powerlosses,
which reduces the usefulness of running these tests longer.

These *_pl_fuzz tests also now match the default number of OPS in
test_relocations.
2024-06-06 17:42:45 -05:00
Christopher Haster b4af52bc72 Implemented SOMEBITS/MOSTBITS emubd powerloss behavior
These emulate powerloss behavior where only some of the bits being
progged are actually progged if there is a powerloss. This behavior was
the original motivation for our ecksums/fcrcs, so it's good to have this
tested.

As a simplification, these only test the extremes:

- LFS_EMUBD_POWERLOSS_SOMEBITS => one bit progged
- LFS_EMUBD_POWERLOSS_MOSTBITS => all-but-one bit progged

Also they flips bits instead of preserving exact partial prog behavior,
but this is allowed (progs can have any intermediate value), has the
same effect as partial progs, and should encourage failed progs.

This required a number of tweaks in emubd: moved powerloss before prog,
moved mutate after powerloss, etc, but these shouldn't affect other
powerloss behaviors. Handling powerloss after prog was only to avoid
power_cycles=1 being useless, it's not strictly required.

Good news is testing so far suggests our ecksum design is sound.
2024-06-06 16:58:18 -05:00
Christopher Haster 9914897e39 Locked down out-of-order writes, more tests
The main test additions are the test_powerloss tests, intended to be
high-level tests over difficult/weird powerloss environments (such as
out-of-order writes!):

- test_powerloss_dir_many - 2242 pls
- test_powerloss_file_many - 8856 pls
- test_powerloss_file_pl_fuzz - 384508 pls
- test_powerloss_filedir_pl_fuzz - 268339 pls

But there was also a bunch of other test movement in the late-stage/
high-level tests. I'm trying to keep the core of these tests somewhat
consistent so we have a nice template to extend for future testing, in
case we want to test other environmentalish concerns, but not all of
these tests make sense in all of these contexts:

                        badblocks  powerloss  relocations  exhaustion
  dir_many                      y          y            y
  dir_fuzz                      y                       y           y
  file_many                     y          y            y
  file_fuzz                     y                       y           y
  fwrite_fuzz                   y                                   y
  orphanzombie_fuzz             y                       y           y
  orphanzombiedir_fuzz          y                       y           y
  file_pl_fuzz                             y            y
  filedir_pl_fuzz                          y            y

Why not:

- dir/file_many+exhaustion? - Needs to be unbounded
- dir/file_fuzz+powerloss? - Takes O(n^2)
- fwrite_fuzz+powerloss? - Takes O(n^2)
- fwrite_fuzz+relocations? - Doesn't really test anything
- orphanzombie*_fuzz+powerloss? - Powerloss kills zombies
- file*_pl_fuzz+badblocks? - PL + Badblocks currently incompactible
- file*_pl_fuzz+exhaustion? - PL + Badblocks currently incompactible

---

Of course, in order to actually get out-of-order write testing working,
we need to implement out-of-order write syncing.

Fortunately this was a simple exercise in placing lfsr_bd_sync calls
before any mdir commits where we may have unsynced data:

- in lfsr_file_sync, to sync any pending file data
- in lfsr_mdir_commit, to sync any mroot/mtree changes

We also call lfsr_bd_sync _after_ mdir commits in case users expect to
sequence any filesystem-external operations such as network, UI, etc. In
theory this could be optional, but no users have really requested it
yet, so leave that for a potential future improvement:

- in lfsr_mdir_commit
- in lfsr_formatinited (really just because we don't go through
  lfsr_mdir_commit)

Note that lfsr_rbyd_commit has been relaxed in the scheme. It only
flushes caches, and does _not_ call lfsr_bd_sync. This is useful for
allowing multiple B-tree nodes to be written out-of-order, also long as
the whole thing is synchronized before any mdir commit.

All of these lfsr_bd_sync calls add a bit of code, but not really an
amount to care about:

           code          stack
  before: 33678           2600
  after:  33766 (+0.3%)   2600 (+0.0%)
2024-06-01 03:34:32 -05:00
Christopher Haster 4ff7c1f771 Commenting out outdated functions for now
This makes it easier to evaluate the code/stack/etc sizes and run tests
without bringing in all of the outdated code.

I guess this officially makes this branch more-or-less a full rewrite,
though the benefit of commenting vs deleting this code is that it can be
easily pulled back in when useful.
2023-06-16 01:51:29 -05:00
Christopher Haster 91ad673c45 Cleaned up a few additional commit corner cases
- General cleanup from integration, including cleaning up some older
  commit code
- Partial-prog tests do not make sense when prog_size == block_size
  (there can't be partial-progs!)
- Fixed signed-comparison issue in modified filebd
2022-12-17 12:42:05 -06:00
Christopher Haster 52dd83096b Initial implementation of forward-looking erase-state CRCs
This change is necessary to handle out-of-order writes found by pjsg's
fuzzing work.

The problem is that it is possible for (non-NOR) block devices to write
pages in any order, or to even write random data in the case of a
power-loss. This breaks littlefs's use of the first bit in a page to
indicate the erase-state.

pjsg notes this behavior is documented in the W25Q here:
https://community.cypress.com/docs/DOC-10507

---

The basic idea here is to CRC the next page, and use this "erase-state CRC" to
check if the next page is erased and ready to accept programs.

.------------------. \   commit
|     metadata     | |
|                  | +---.
|                  | |   |
|------------------| |   |
| erase-state CRC -----. |
|------------------| | | |
|   commit CRC    ---|-|-'
|------------------| / |
|     padding      |   | padding (doesn't need CRC)
|                  |   |
|------------------| \ | next prog
|     erased?      | +-'
|        |         | |
|        v         | /
|                  |
|                  |
'------------------'

This is made a bit annoying since littlefs doesn't actually store the
page (prog_size) in the superblock, since it doesn't need to know the
size for any other operation. We can work around this by storing both
the CRC and size of the next page when necessary.

Another interesting note is that we don't need to any bit tweaking
information, since we read the next page every time we would need to
know how to clobber the erase-state CRC. And since we only read
prog_size, this works really well with our caching, since the caches
must be a multiple of prog_size.

This also brings back the internal lfs_bd_crc function, in which we can
use some optimizations added to lfs_bd_cmp.

Needs some cleanup but the idea is passing most relevant tests.
2022-12-17 12:42:05 -06:00