Adopted lazy orphaned mdir drops
This ended up being much less of a simplification than I hoped it would.
It's still easier/more efficient to revert to a relocation in most cases
when dropping in an mdir split, and the small gain from simplifying how
drops/commits interact is overshadowed by the code duplication necessary
to separate lfsr_mdir_drop out from lfsr_mdir_commit:
code stack
before: 30952 2528
after: 31280 (+1.1%) 2648 (+4.7%)
Still, this does at least simplify the logical corner cases (we don't
need to abort commits when droppable anymore), and lfsr_mdir_drop is
ultimately necessary for supporting lazy file creation.
Also having a fix-orphans step during mount allows other littlefs
implementations the option to create orphanned mdirs without compat
issues. So this ends up the more flexible approach.
It _might_ be worth having both eager mdir drops and an explicit
lfsr_mdir_drop for lazy file creation in the future, but I doubt this
will end up worth the code duplication...
---
Oh right, I forgot to actually describe this change.
This trades eager mdir drops:
1. Drop mdirs from the mtree immediately as soon as their weight goes
to zero.
For lazy mdir drops:
1. Drop mdirs from the mtree in a second commit.
2. Scan and drop orphaned mdirs on the first write after mount.
This sounds very similar to the previous "deorphan" scan, which risked
an extreme performance cost during mount, but it should be noted this
orphan scan only needs to touch every mdir once. This makes it no worse
than the overhead of actually mounting the filesystem.
We can also keep an eye out for orphaned mdirs when we mount, so no
extra scan is needed unless there was an unlucky powerloss.
Eager mdir dropping sounds simpler, but thanks to deferred commits
introduces some subtle complexity around aborting commits that would
drop an mdir to zero. Remember commits are viewable on-disk as soon as a
commit completes.
In _theory_, lazy mdir drops simplify the logic around committing to
mdirs.
Though the real kicker is that lazy mdir drops are required for lazy file
creation.
The current idea for lazy file creation involves tracking mid-less
opened-but-not-yet-created files. These files can have bshrubs, so they
need space on an mdir somewhere. But they aren't actually created yet,
so they don't have an mid.
This is fine (though it's probably going to be tricky) as long as we
allocate an mid on file sync, but there is always a risk of losing power
with mdirs that contain only RAM-backed files. Fortunately, no-mids
means no orphaned files, but it does mean orphaned mdirs with no synced
contents.
Long story short, lazy mdir drops are currently a necessary evil, and
logical simplification, that unfortunately comes with some cost.
This commit is contained in:
@@ -610,6 +610,10 @@ typedef struct lfs {
|
||||
uint8_t grm_g[LFSR_GRM_DSIZE];
|
||||
uint8_t grm_d[LFSR_GRM_DSIZE];
|
||||
|
||||
// TODO we should put this flag somewhere, should lfs_t have a general
|
||||
// purpose flags field? this has been useful for lfsr_file_t
|
||||
bool hasorphans;
|
||||
|
||||
uint8_t mbits;
|
||||
lfsr_mdir_t mroot;
|
||||
lfsr_mtree_t mtree;
|
||||
|
||||
@@ -4411,3 +4411,105 @@ code = '''
|
||||
assert(memcmp(&magic[8], "littlefs", 8) == 0);
|
||||
'''
|
||||
|
||||
|
||||
## Orphaned mdirs ##
|
||||
|
||||
# orphaned mdirs can happen if we lose power, test we can clean them up
|
||||
[cases.test_mtree_orphans]
|
||||
defines.N = 320
|
||||
defines.ORPHANS = [1, 2, 3, 4]
|
||||
defines.SEED = 42
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
const char *alphas = "abcdefghijklmnopqrstuvwxyz";
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
lfs_alloc_ack(&lfs);
|
||||
// remove root bookmark for now
|
||||
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
|
||||
LFSR_ATTR(0, RM, -1, NULL()))) => 0;
|
||||
|
||||
// create entries
|
||||
lfsr_mdir_t mdir;
|
||||
lfsr_mtree_lookup(&lfs,
|
||||
lfs_smax32(
|
||||
lfsr_mtree_weight(&lfs) - lfsr_mweight(&lfs),
|
||||
0),
|
||||
&mdir) => 0;
|
||||
for (lfs_size_t i = 0; i < N; i++) {
|
||||
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(mdir.mid, REG, +1,
|
||||
BUF(&alphas[i % 26], 1)))) => 0;
|
||||
|
||||
uint8_t buffer[4];
|
||||
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
||||
buffer, 4) => 1;
|
||||
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
||||
|
||||
mdir.mid += 1;
|
||||
}
|
||||
lfsr_mid_t old_weight = lfsr_mtree_weight(&lfs);
|
||||
|
||||
// this test only works with a full mtree
|
||||
LFS_ASSERT(lfsr_mtree_isbtree(&lfs));
|
||||
|
||||
// bypass the mdir logic and create some orphans
|
||||
uint32_t prng = SEED;
|
||||
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
||||
// note we should never have orphan.mid=0
|
||||
lfsr_bid_t bid_ = ((TEST_PRNG(&prng)
|
||||
% (lfsr_mtree_weight(&lfs)/lfsr_mweight(&lfs))) + 1)
|
||||
* lfsr_mweight(&lfs);
|
||||
|
||||
// manually allocate/commit an empty mdir, otherwise
|
||||
// lfsr_mdir_commit automatically cleans up empty mdirs
|
||||
lfsr_mptr_t mptr;
|
||||
for (lfs_size_t j = 0; j < 2; j++) {
|
||||
lfsr_rbyd_t rbyd;
|
||||
lfsr_rbyd_alloc(&lfs, &rbyd) => 0;
|
||||
|
||||
lfsr_rbyd_commit(&lfs, &rbyd, LFSR_ATTRS(
|
||||
LFSR_ATTR(0, REG, +1, BUF("a", 1)),
|
||||
LFSR_ATTR(0, RM, -1, NULL()))) => 0;
|
||||
mptr.blocks[j] = rbyd.blocks[0];
|
||||
}
|
||||
|
||||
// commit orphan to tree
|
||||
uint8_t mptr_buf[LFSR_MPTR_DSIZE];
|
||||
lfsr_mtree_commit(&lfs, LFSR_ATTRS(
|
||||
LFSR_ATTR(bid_,
|
||||
MDIR, +lfsr_mweight(&lfs),
|
||||
FROMMPTR(&mptr, mptr_buf)))) => 0;
|
||||
}
|
||||
LFS_ASSERT(lfsr_mtree_weight(&lfs) > old_weight);
|
||||
|
||||
// trigger lfsr_fs_fixorphans
|
||||
lfs.hasorphans = true;
|
||||
lfsr_fs_preparemutation(&lfs) => 0;
|
||||
|
||||
// this should have removed all of our orphans
|
||||
LFS_ASSERT(lfsr_mtree_weight(&lfs) == old_weight);
|
||||
|
||||
// try looking up each entry
|
||||
lfs_size_t i = 0;
|
||||
for (lfs_ssize_t mid = 0;
|
||||
mid < lfs_smax32(
|
||||
lfsr_mtree_weight(&lfs),
|
||||
lfsr_mweight(&lfs));
|
||||
mid += lfsr_mweight(&lfs)) {
|
||||
lfsr_mdir_t mdir;
|
||||
lfsr_mtree_lookup(&lfs, mid, &mdir) => 0;
|
||||
for (; lfsr_mdir_rid(&lfs, &mdir) < mdir.rbyd.weight;
|
||||
mdir.mid += 1) {
|
||||
uint8_t buffer[4];
|
||||
lfsr_mdir_get(&lfs, &mdir, mdir.mid, LFSR_TAG_REG,
|
||||
buffer, 4) => 1;
|
||||
assert(memcmp(buffer, &alphas[i % 26], 1) == 0);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
assert(i == N);
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user