Skip to content

Commit

Permalink
Adopted Brent's algorithm for cycle detection
Browse files Browse the repository at this point in the history
The previous cycle detection algorithm (a naive check against the largest
possible tail list) is simple and gets the job done, but has the potential to
take a very long time on disks with many blocks. Brent's algorithm, on
the other hand, takes at most 2x the number of blocks in the tail list.

Originally naive cycle detection was chosen over Floyd's algorithm to
avoid the extra complexity of managing two desynced traversals for every
traversal of the tail list, but Brent's algorithm is very well suited for our
use case, requiring only we keep track of an additional mdir pointer on the
stack as we traverse.
  • Loading branch information
geky committed Dec 17, 2022
1 parent c2147c4 commit 1278ec1
Showing 1 changed file with 54 additions and 20 deletions.
74 changes: 54 additions & 20 deletions lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,12 @@ static inline int lfs_pair_cmp(
paira[0] == pairb[1] || paira[1] == pairb[0]);
}

#ifndef LFS_READONLY
static inline bool lfs_pair_sync(
static inline bool lfs_pair_issync(
const lfs_block_t paira[2],
const lfs_block_t pairb[2]) {
return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
(paira[0] == pairb[1] && paira[1] == pairb[0]);
}
#endif

static inline void lfs_pair_fromle32(lfs_block_t pair[2]) {
pair[0] = lfs_fromle32(pair[0]);
Expand Down Expand Up @@ -4110,14 +4108,23 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {

// scan directory blocks for superblock and any global updates
lfs_mdir_t dir = {.tail = {0, 1}};
lfs_block_t cycle = 0;
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
// detect cycles with Brent's algorithm
if (lfs_pair_issync(dir.tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
err = LFS_ERR_CORRUPT;
goto cleanup;
}
cycle += 1;
if (tortoise_i == tortoise_period) {
tortoise[0] = dir.tail[0];
tortoise[1] = dir.tail[1];
tortoise_i = 0;
tortoise_period *= 2;
}
tortoise_i += 1;

// fetch next block in tail list
lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
Expand Down Expand Up @@ -4268,13 +4275,22 @@ int lfs_fs_rawtraverse(lfs_t *lfs,
}
#endif

lfs_block_t cycle = 0;
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
while (!lfs_pair_isnull(dir.tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
// detect cycles with Brent's algorithm
if (lfs_pair_issync(dir.tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
return LFS_ERR_CORRUPT;
}
cycle += 1;
if (tortoise_i == tortoise_period) {
tortoise[0] = dir.tail[0];
tortoise[1] = dir.tail[1];
tortoise_i = 0;
tortoise_period *= 2;
}
tortoise_i += 1;

for (int i = 0; i < 2; i++) {
int err = cb(data, dir.tail[i]);
Expand Down Expand Up @@ -4353,13 +4369,22 @@ static int lfs_fs_pred(lfs_t *lfs,
// iterate over all directory directory entries
pdir->tail[0] = 0;
pdir->tail[1] = 1;
lfs_block_t cycle = 0;
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
while (!lfs_pair_isnull(pdir->tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
// detect cycles with Brent's algorithm
if (lfs_pair_issync(pdir->tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
return LFS_ERR_CORRUPT;
}
cycle += 1;
if (tortoise_i == tortoise_period) {
tortoise[0] = pdir->tail[0];
tortoise[1] = pdir->tail[1];
tortoise_i = 0;
tortoise_period *= 2;
}
tortoise_i += 1;

if (lfs_pair_cmp(pdir->tail, pair) == 0) {
return 0;
Expand Down Expand Up @@ -4409,13 +4434,22 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
// use fetchmatch with callback to find pairs
parent->tail[0] = 0;
parent->tail[1] = 1;
lfs_block_t cycle = 0;
lfs_block_t tortoise[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
lfs_size_t tortoise_i = 1;
lfs_size_t tortoise_period = 1;
while (!lfs_pair_isnull(parent->tail)) {
if (cycle >= lfs->cfg->block_count/2) {
// loop detected
// detect cycles with Brent's algorithm
if (lfs_pair_issync(parent->tail, tortoise)) {
LFS_WARN("Cycle detected in tail list");
return LFS_ERR_CORRUPT;
}
cycle += 1;
if (tortoise_i == tortoise_period) {
tortoise[0] = parent->tail[0];
tortoise[1] = parent->tail[1];
tortoise_i = 0;
tortoise_period *= 2;
}
tortoise_i += 1;

lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
LFS_MKTAG(0x7ff, 0, 0x3ff),
Expand Down Expand Up @@ -4532,7 +4566,7 @@ static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
}
lfs_pair_fromle32(pair);

if (!lfs_pair_sync(pair, pdir.tail)) {
if (!lfs_pair_issync(pair, pdir.tail)) {
// we have desynced
LFS_DEBUG("Fixing half-orphan "
"{0x%"PRIx32", 0x%"PRIx32"} "
Expand Down

0 comments on commit 1278ec1

Please sign in to comment.