Reverted gc-restart on flag change
Thinking about this more, we probably don't want to entangle
lfsr_fs_mkconsistent/ckmeta/etc and lfsr_fs_gc:
- lfsr_fs_ckmeta/ckdata are readonly and don't need to clobber
traversals. The system can make more progress if these use separate
states.
- We already need a bit of code to force traversals to restart for
lfsr_fs_ckmeta/ckdata, so these already aren't simple wrappers.
- lfsr_fs_mkconsistent should also probably not invalidate gc traversals
when the filesystem is already consistent. It is called by... checks
notes... every function that writes to disk.
This could be fixed in lfsr_fs_mkconsistent, but it'd be pretty close
to just calling lfsr_mtree_gc...
- We don't really benefit from reusing the gc traversal state.
lfsr_fs_mkconsistent/ckmeta/etc aren't on the stack hot-path, so the
stack usage is more-or-less free (though I realize this depends on
what functions are called in a given system).
- Calling lfsr_fs_gc can actually be a detriment for code size when
considering link-time-gc (not related to fs-gc), since it will drag in
the function when we don't need the traversal-invalidation features.
- Calling lfsr_fs_gc vs lfsr_mtree_gc shouldn't really be a significant
code size difference. We should probably look into lfsr_mtree_gc,
which is called from many places, instead of tangling everything
together...
So this commit reverts gc-restarts and brings back gc masking on flag
change.
At the very least, moving all the code around led to a bit of code
savings:
code stack
before gc-restart: 36316 2680
gc-restart: 36068 (-0.7%) 2680 (+0.0%)
after gc-restart: 36240 (-0.2%) 2680 (+0.0%)
This commit is contained in:
@@ -12928,32 +12928,97 @@ failed:;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lfsr_fs_fixorphans(lfs_t *lfs) {
|
||||
// LFS_T_MKCONSISTENT really just removes orphans
|
||||
lfsr_traversal_t t = LFSR_TRAVERSAL(
|
||||
LFS_T_MTREEONLY | LFS_T_MKCONSISTENT);
|
||||
while (true) {
|
||||
int err = lfsr_mtree_gc(lfs, &t,
|
||||
NULL, NULL);
|
||||
if (err) {
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// prepare the filesystem for mutation
|
||||
int lfsr_fs_mkconsistent(lfs_t *lfs) {
|
||||
// leave this up to lfsr_fs_gc
|
||||
return lfsr_fs_gc(lfs, -1, LFS_GC_MTREEONLY | LFS_GC_MKCONSISTENT);
|
||||
// fix pending grms
|
||||
if (lfsr_grm_count(lfs) > 0) {
|
||||
if (lfsr_grm_count(lfs) == 2) {
|
||||
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32" %"PRId32".%"PRId32,
|
||||
lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits,
|
||||
lfsr_mid_rid(lfs, lfs->grm.mids[0]),
|
||||
lfsr_mid_bid(lfs, lfs->grm.mids[1]) >> lfs->mdir_bits,
|
||||
lfsr_mid_rid(lfs, lfs->grm.mids[1]));
|
||||
} else if (lfsr_grm_count(lfs) == 1) {
|
||||
LFS_DEBUG("Fixing grm %"PRId32".%"PRId32,
|
||||
lfsr_mid_bid(lfs, lfs->grm.mids[0]) >> lfs->mdir_bits,
|
||||
lfsr_mid_rid(lfs, lfs->grm.mids[0]));
|
||||
}
|
||||
|
||||
int err = lfsr_fs_fixgrm(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// fix orphaned files
|
||||
//
|
||||
// this must happen after fixgrm, since removing orphaned files risks
|
||||
// outdating the grm
|
||||
//
|
||||
if (lfsr_f_hasorphans(lfs->flags)) {
|
||||
LFS_DEBUG("Fixing orphans...");
|
||||
|
||||
int err = lfsr_fs_fixorphans(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check the filesystem for metadata errors
|
||||
int lfsr_fs_ckmeta(lfs_t *lfs) {
|
||||
// we want a full traversal, so make sure no gc is currently running
|
||||
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
|
||||
lfsr_omdir_close(lfs, &lfs->gc.o.o);
|
||||
// we leave this up to lfsr_mtree_gc
|
||||
lfsr_traversal_t t = LFSR_TRAVERSAL(LFS_T_CKMETA);
|
||||
while (true) {
|
||||
int err = lfsr_mtree_gc(lfs, &t,
|
||||
NULL, NULL);
|
||||
if (err) {
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// leave this up to lfsr_fs_gc
|
||||
return lfsr_fs_gc(lfs, -1, LFS_GC_CKMETA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check the filesystem for metadata + data errors
|
||||
int lfsr_fs_ckdata(lfs_t *lfs) {
|
||||
// we want a full traversal, so make sure no gc is currently running
|
||||
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
|
||||
lfsr_omdir_close(lfs, &lfs->gc.o.o);
|
||||
// we leave this up to lfsr_mtree_gc
|
||||
lfsr_traversal_t t = LFSR_TRAVERSAL(LFS_T_CKMETA | LFS_T_CKDATA);
|
||||
while (true) {
|
||||
int err = lfsr_mtree_gc(lfs, &t,
|
||||
NULL, NULL);
|
||||
if (err) {
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// leave this up to lfsr_fs_gc
|
||||
return lfsr_fs_gc(lfs, -1, LFS_GC_CKMETA | LFS_GC_CKDATA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// perform any pending janitorial work
|
||||
@@ -13015,22 +13080,23 @@ int lfsr_fs_gc(lfs_t *lfs, lfs_soff_t steps, uint32_t flags) {
|
||||
// checkpoint the allocator to maximize any lookahead scans
|
||||
lfs_alloc_ckpoint(lfs);
|
||||
|
||||
// flags mismatch? restart traversal
|
||||
if (lfsr_omdir_isopen(lfs, &lfs->gc.o.o)
|
||||
&& (flags != (lfs->gc.o.o.flags & (
|
||||
LFS_T_MTREEONLY
|
||||
| LFS_T_MKCONSISTENT
|
||||
| LFS_T_LOOKAHEAD
|
||||
| LFS_T_COMPACT
|
||||
| LFS_T_CKMETA
|
||||
| LFS_T_CKDATA)))) {
|
||||
lfsr_omdir_close(lfs, &lfs->gc.o.o);
|
||||
}
|
||||
|
||||
// start a new traversal?
|
||||
if (!lfsr_omdir_isopen(lfs, &lfs->gc.o.o)) {
|
||||
lfs->gc = LFSR_TRAVERSAL(flags);
|
||||
lfsr_omdir_open(lfs, &lfs->gc.o.o);
|
||||
|
||||
// existing traversal?
|
||||
} else {
|
||||
// note that we mask flags (except mtreeonly)! if you change flags
|
||||
// mid-traversal, the result is equivalent to the worst-case set
|
||||
// of flags
|
||||
lfs->gc.o.o.flags &= ~(
|
||||
LFS_GC_MKCONSISTENT
|
||||
| LFS_GC_LOOKAHEAD
|
||||
| LFS_GC_COMPACT
|
||||
| LFS_GC_CKMETA
|
||||
| LFS_GC_CKDATA
|
||||
) | flags;
|
||||
}
|
||||
|
||||
// progress gc
|
||||
|
||||
@@ -142,6 +142,140 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that adding flags doesn't break lookahead
|
||||
[cases.test_gc_lookahead_add_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "spider",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
|
||||
// check the file contents
|
||||
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that removing flags invalidates lookahead
|
||||
[cases.test_gc_lookahead_remove_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "spider",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_LOOKAHEAD
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_CANLOOKAHEAD);
|
||||
|
||||
// check the file contents
|
||||
lfsr_file_open(&lfs, &file, "spider", LFS_O_RDONLY) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
# test that compact can make progress in isolation
|
||||
[cases.test_gc_compact_progress]
|
||||
@@ -334,6 +468,203 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that adding flags doesn't break compact
|
||||
[cases.test_gc_compact_add_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# set compact thresh to minimum
|
||||
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
|
||||
// hack, don't use the internals like this
|
||||
uint8_t wbuf[SIZE];
|
||||
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one traversal + one step
|
||||
while (true) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done (twice for compact)
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
|
||||
// check we can still read the file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that removing flags invalidates compact
|
||||
[cases.test_gc_compact_remove_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = [
|
||||
'FILE_BUFFER_SIZE/2',
|
||||
'2*FILE_BUFFER_SIZE',
|
||||
'BLOCK_SIZE/2',
|
||||
'BLOCK_SIZE',
|
||||
'2*BLOCK_SIZE',
|
||||
'8*BLOCK_SIZE',
|
||||
]
|
||||
# set compact thresh to minimum
|
||||
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// write to our mdir until >gc_compact_thresh full
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
|
||||
// hack, don't use the internals like this
|
||||
uint8_t wbuf[SIZE];
|
||||
while ((file.o.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) {
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one traversal + one step
|
||||
while (true) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
|
||||
// internal traversal done?
|
||||
if (lfs.omdirs != &lfs.gc.o.o) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_COMPACT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done (twice for compact)
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_UNCOMPACTED);
|
||||
|
||||
// check we can still read the file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
# test that mkconsistent can make progress in isolation
|
||||
[cases.test_gc_mkconsistent_progress]
|
||||
@@ -654,6 +985,235 @@ code = '''
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that adding flags doesn't break mkconsistent
|
||||
[cases.test_gc_mkconsistent_add_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.COMPACT = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# <=2 => grm-able
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [3, 100]
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// create two files
|
||||
lfsr_file_t file1;
|
||||
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint8_t wbuf1[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file1) => 0;
|
||||
|
||||
lfsr_file_t file2;
|
||||
lfsr_file_open(&lfs, &file2, "octopus",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint8_t wbuf2[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file2) => 0;
|
||||
|
||||
// create this many orphaned files
|
||||
//
|
||||
// anytime we close a not-yet-created desync file, we create an
|
||||
// orphan, but note we need these to be different files, and we need
|
||||
// to close them after all open calls, otherwise we just end up with
|
||||
// one orphan (littlefs is eager to clean up orphans)
|
||||
//
|
||||
lfsr_file_t orphans[ORPHANS];
|
||||
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "jellyfish%03x", i);
|
||||
lfsr_file_open(&lfs, &orphans[i], name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
||||
}
|
||||
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
||||
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_MKCONSISTENT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
|
||||
// check we can still read the files
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file1) => 0;
|
||||
lfsr_file_close(&lfs, &file2) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
||||
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file1) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
||||
|
||||
lfsr_file_rewind(&lfs, &file2) => 0;
|
||||
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file1) => 0;
|
||||
lfsr_file_close(&lfs, &file2) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
# test that removing flags invalidates mkconsistent
|
||||
[cases.test_gc_mkconsistent_remove_flags]
|
||||
defines.GC_STEPS = 1
|
||||
defines.LOOKAHEAD = [false, true]
|
||||
defines.COMPACT = [false, true]
|
||||
defines.CKMETA = [false, true]
|
||||
defines.CKDATA = [false, true]
|
||||
defines.SIZE = 'FILE_BUFFER_SIZE/2'
|
||||
# <=2 => grm-able
|
||||
# >2 => requires orphans
|
||||
defines.ORPHANS = [3, 100]
|
||||
# we need something to keep the traversal running
|
||||
if = 'CKMETA || CKDATA'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
|
||||
uint32_t prng = 42;
|
||||
|
||||
// create two files
|
||||
lfsr_file_t file1;
|
||||
lfsr_file_open(&lfs, &file1, "cuttlefish",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint8_t wbuf1[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file1, wbuf1, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file1) => 0;
|
||||
|
||||
lfsr_file_t file2;
|
||||
lfsr_file_open(&lfs, &file2, "octopus",
|
||||
LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint8_t wbuf2[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file2, wbuf2, SIZE) => SIZE;
|
||||
lfsr_file_sync(&lfs, &file2) => 0;
|
||||
|
||||
// create this many orphaned files
|
||||
//
|
||||
// anytime we close a not-yet-created desync file, we create an
|
||||
// orphan, but note we need these to be different files, and we need
|
||||
// to close them after all open calls, otherwise we just end up with
|
||||
// one orphan (littlefs is eager to clean up orphans)
|
||||
//
|
||||
lfsr_file_t orphans[ORPHANS];
|
||||
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
||||
char name[256];
|
||||
sprintf(name, "jellyfish%03x", i);
|
||||
lfsr_file_open(&lfs, &orphans[i], name,
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0;
|
||||
}
|
||||
for (lfs_size_t i = 0; i < ORPHANS; i++) {
|
||||
lfsr_file_close(&lfs, &orphans[i]) => 0;
|
||||
}
|
||||
|
||||
// expect dirty initial state or else our test doesn't work
|
||||
struct lfs_fsinfo fsinfo;
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
assert(lfs.omdirs != &lfs.gc.o.o);
|
||||
|
||||
// run GC one step
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
LFS_GC_MKCONSISTENT
|
||||
| ((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
assert(lfs.omdirs == &lfs.gc.o.o);
|
||||
|
||||
// change flags and run GC until our traversal is done
|
||||
while (lfs.omdirs == &lfs.gc.o.o) {
|
||||
lfsr_fs_gc(&lfs, GC_STEPS,
|
||||
((LOOKAHEAD) ? LFS_GC_LOOKAHEAD : 0)
|
||||
| ((COMPACT) ? LFS_GC_COMPACT : 0)
|
||||
| ((CKMETA) ? LFS_GC_CKMETA : 0)
|
||||
| ((CKDATA) ? LFS_GC_CKDATA : 0)) => 0;
|
||||
}
|
||||
|
||||
// we should _not_ make progress
|
||||
lfsr_fs_stat(&lfs, &fsinfo) => 0;
|
||||
assert(fsinfo.flags & LFS_I_INCONSISTENT);
|
||||
|
||||
// check we can still read the files
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_file_close(&lfs, &file1) => 0;
|
||||
lfsr_file_close(&lfs, &file2) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
|
||||
lfsr_file_open(&lfs, &file1, "cuttlefish", LFS_O_RDONLY) => 0;
|
||||
lfsr_file_open(&lfs, &file2, "octopus", LFS_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
lfsr_file_rewind(&lfs, &file1) => 0;
|
||||
uint8_t rbuf[SIZE];
|
||||
lfsr_file_read(&lfs, &file1, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf1, SIZE) == 0);
|
||||
|
||||
lfsr_file_rewind(&lfs, &file2) => 0;
|
||||
lfsr_file_read(&lfs, &file2, rbuf, SIZE) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf2, SIZE) == 0);
|
||||
}
|
||||
|
||||
lfsr_file_close(&lfs, &file1) => 0;
|
||||
lfsr_file_close(&lfs, &file2) => 0;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
# test we can detect at least fully clobbered blocks
|
||||
[cases.test_gc_ckmeta]
|
||||
|
||||
Reference in New Issue
Block a user