From 1631ca8d78ab5c47525a9dcbd08bb4a4296edb10 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 27 May 2023 17:14:25 -0500 Subject: [PATCH] Reimplemented Brent's cycle detection on top of mtree traversal This is a bit tricky because our tortoise state is now quite large thanks to how we are nesting traversals: - Current mdir pair - Current mtree block+trunk - Current btree block+trunk? (TODO) - Others? (TODO) This also raises some questions about what constitutes a cycle in our btrees. Since they are strictly CoW, they should be strictly DAGs worst case. But is that still true when considering that btree nodes can contain multiple trunk versions? To be safe, I'm currently including the trunks in our tortoise state, but it may be possible to relax this in the future. --- lfs.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lfs.c b/lfs.c index 9a741dc4..d3c545ef 100644 --- a/lfs.c +++ b/lfs.c @@ -4314,6 +4314,7 @@ typedef struct lfsr_btree_traversal { #define LFSR_BTREE_TRAVERSAL_INIT ((lfsr_btree_traversal_t){ \ .bid = 0, \ .rid = 0, \ + .branch.block = 0, \ .branch.trunk = 0, \ .branch.weight = 0, \ }) @@ -5511,9 +5512,17 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid, // incremental mtree traversal typedef struct lfsr_mtree_traversal { - uint8_t flags; + // core traversal state lfsr_mdir_t mdir; lfsr_btree_traversal_t mtraversal; + uint8_t flags; + + // cycle detection state + uint8_t tortoise_power; + lfs_size_t tortoise_step; + lfsr_mpair_t tortoise_mpair; + lfs_block_t tortoise_mtree_block; + lfs_size_t tortoise_mtree_trunk; } lfsr_mtree_traversal_t; enum { @@ -5524,16 +5533,40 @@ enum { .flags = _flags, \ .mdir.rbyd.trunk = 0, \ .mtraversal = LFSR_BTREE_TRAVERSAL_INIT, \ + .tortoise_power = 0, \ + .tortoise_step = 0, \ }) static int lfsr_mtree_traversal_next(lfs_t *lfs, lfsr_mtree_traversal_t *traversal, lfs_size_t *mid_, lfsr_tag_t *tag_, lfsr_data_t *data_) { + // detect cycles with Brent's algorithm + if (traversal->mdir.rbyd.trunk != 0 + && lfsr_mpair_eq(lfsr_mdir_mpair(&traversal->mdir), + traversal->tortoise_mpair) + && traversal->mtraversal.branch.block + == traversal->tortoise_mtree_block + && traversal->mtraversal.branch.trunk + == traversal->tortoise_mtree_trunk) { + LFS_ERROR("Cycle detected during mtree traversal"); + return LFS_ERR_CORRUPT; + } + if (traversal->tortoise_step + == ((lfs_size_t)1 << traversal->tortoise_power)) { + traversal->tortoise_mpair = lfsr_mdir_mpair(&traversal->mdir); + traversal->tortoise_mtree_block = traversal->mtraversal.branch.block; + traversal->tortoise_mtree_trunk = traversal->mtraversal.branch.trunk; + traversal->tortoise_step = 0; + traversal->tortoise_power += 1; + } + traversal->tortoise_step += 1; + // new traversal? start with 0x{0,1} // // note we make sure to include fake mroots! // if (traversal->mdir.rbyd.trunk == 0) { + // fetch the first mroot 0x{0,1} int err = lfsr_mdir_fetch(lfs, &traversal->mdir, -1, LFSR_MPAIR(0, 1), NULL); if (err) {