Commit Graph

3 Commits

Author SHA1 Message Date
Christopher Haster e744106f77 t: Implemented a simple traversal clobber scheme
This adds lfsr_opened_clobber which can be called to clobber any open
traversals related to an mid, or all traversals if mid=-1. Clobbering
here means throw away any in-progress btraversals and move to the next
mid. We need to do this in several places to avoid outdated references
to btrees.

The other option would be to treat traversals like additional unsynced
file handles, add them to the lookahead buffer, copy shrubs during
compaction, etc, but I don't think we want to pay this cost since the
underlying data is otherwise inaccessible. No reason to check/repair
blocks we're not using anymore...

To make this work, LFS_BTRAVERSAL(bid) now supports resuming from a
specific bid, in lfsr_mtraversal_t we use this to resume mtree traversal
from a specific mid when clobbered.

---

Other changes:

- lfsr_mdir_commit now marks all removed mdirs with LFS_F_ZOMBIE, and
  updating related dir positions is done in lfsr_remove/lfsr_rename.

  I was originally planning to use LFS_F_ZOMBIE to clobber traversals as
  well, but it didn't work out.

- Added lfsr_fs_weight, which returns the effective mdir/mtree weight,
  including inlined-in-mroot mdirs.

- Fixed did-mask miscalculation in lfsr_mkdir where fs/mtree weight
  wasn't shifted by mdir_bits. This probably just went unnoticed during
  some mid refactoring.

- Changed traversals to only traverse _unsynced_ opened files. No reason
  to traverse files we know match disk. This also makes is so only
  unsynced files need to worry about clobbering traversals.

  This has the catch that we need to point to the traversing file handle
  somehow so we can clobber correctly. The (hacky?) solution is to point
  to the next pointer itself, which tells us both where to go next, and
  what file handle we are currently traversing.

- Moved LFS_F_UNSYNC flags to before file operations, instead of after.
  This is needed for the above traverse-unsync-only logic in case we
  alloc in the middle of a file operation.

Code changes:

           code          stack
  before: 34454           2544
  after:  34682 (+0.7%)   2544 (+0.0%)

Also added some specific tests over corner cases caused by traversing
and mutating the filesystem at the same time.

Unfortunately these aren't passing yet. Our mid-clobbering logic doesn't
handle mid insertion correctly, so we end up clobbering more traversals
than we need to...
2024-06-20 13:12:59 -05:00
Christopher Haster 635e1fe8d4 t: Added lookahead to lfsr_traversal_t, adopted in lfs_alloc
This sort of turned into a complete refactor of lfs_alloc in order to
move/reuse the lookahead buffer filling logic into lfsr_fs_traverse.

lfs_alloc now calls lfsr_fs_traverse to fill the lookahead buffer when
no more blocks are available, but also you can too with lfsr_traversal_t
+ LFS_T_LOOKAHEAD.

The one big caveat being if any mutation happens to the filesystem, any
incomplete lookahead needs to be tossed out. To help with this,
lfsr_traversal_read now returns LFS_ERR_BUSY (-16) instead of
LFS_ERR_NOENT (-2) if the filesystem has been modified since the
traversal was opened.

Note that by default lfsr_traversal_t will still try to keep traversing
blocks, but can be told to terminate immediately with LFS_T_EXCL.
Continuing the traversal is probably desired for checking checksums,
debugging, etc, as otherwise you could end up looping over only the
first couple blocks in a write-heavy system, but if you are trying to
populate the lookahead buffer you probably want to just abort and start
over.

I considered adding a flags field to lfs_tinfo for this, but decided
against it since it would be the only place in the current API where we
don't use error codes to convey behavior-changing information. Though
this may be worth reconsidering at some point...

---

In reworking lfs_alloc, a lot of the internal logic was broken up into
specific functions:

- lfs_alloc_ckpoint - checkpoint the allocator
- lfs_alloc_discard - discard any lookahead
- lfs_alloc_shift - discard/shift lookahead if progress can be made
- lfs_alloc_markinuse - mark a block as in-use
- lfs_alloc_markfree - mark any remaining blocks as free
- lfs_alloc_findnext - find the next free block in lookahead

If anything this probably makes lfs_alloc more readable, though the
original motivation was to allow lfsr_traversal_t to only shift/zero the
lookahead buffer if there's a chance we can make progress.

This was based on upstream work by opilat and myself.

Code changes:

           code          stack
  before: 34226           2560
  after:  34474 (+0.7%)   2552 (-0.3%)
2024-06-20 13:11:41 -05:00
Christopher Haster 670b9fbf99 t: Implemented rudimentary lfsr_traversal_t and related functions
This adds the lfsr_traversal_t object, which encapsulates a traversal
over all blocks in the filesystem.

This replaces the earlier lfs_fs_traverse function, but is sort of
"inside-out" in that instead of taking a callback, an lfsr_traversal_t
object can be read from to return lfs_tinfo structs that describe the
blocks in our system:

  lfsr_traversal_open(&lfs, &t) => 0;
  lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
  tinfo.btype => LFS_BTYPE_MDIR;
  tinfo.block => 0x0;
  lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
  tinfo.btype => LFS_BTYPE_MDIR;
  tinfo.block => 0x1;
  lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
  tinfo.btype => LFS_BTYPE_DATA;
  tinfo.block => 0x42;
  lfsr_traversal_read(&lfs, &t, &tinfo) => LFS_ERR_NOENT;
  lfsr_traversal_close(&lfs, &t) => 0;

This is more flexible, allowing for aborted traversals, yielding,
rewinding, etc, but also more complicated to implement, since it
requires all traversal state to be stored explicitly.

Fortunately, since we needed to reimplement filesystem traversals
anyways, I was able to build this into the new system from the start
using a small state machine to drive the traversal internally. So all
that was really needed was a bit of window dressing, adding
LFS_TYPE_TRAVERSAL to track open traversals, logic to handle
invalidating traversals on file close, mutation, etc...

Which, uh, that last one is not implemented yet. Interactions with other
filesystem operations gets messy, so I figured I'd go ahead and commit
what is currently working.

Ugh, and tests. The biggest downside of adding lfsr_traversal_t is how
many more corner-cases it adds to the system...

lfsr_traversal_t is going to be a work-in-progress for a bit...

---

lfsr_traversal_t also adds a really interesting path towards more access
to advanced low-level operations, such as checking metadata/data
checksums, incrementally progressing the garbage collector, even
repairing bad metadata/data blocks eventually.

Currently implemented is LFS_T_CKMETADATA and LFS_T_CKDATA to check
metadata and data checksums respectively. This is the first feature that
actually allows you to validate data checksums.

Code changes so far:

           code          stack
  before: 33886           2560
  after:  34226 (+1.0%)   2560 (+0.0%)
2024-06-20 13:09:24 -05:00