diff --git a/lfs.c b/lfs.c index 27a66e9c..f6001a59 100644 --- a/lfs.c +++ b/lfs.c @@ -607,9 +607,10 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) { } #endif -// indicate allocated blocks have been committed into the filesystem, this -// is to prevent blocks from being garbage collected in the middle of a -// commit operation +// allocations should call this when all allocated blocks are committed to +// the filesystem +// +// after a checkpoint, the block allocator may realloc any untracked blocks static void lfs_alloc_ckpoint(lfs_t *lfs) { lfs->lookahead.ckpoint = lfs->block_count; } @@ -624,14 +625,16 @@ static void lfs_alloc_drop(lfs_t *lfs) { #ifndef LFS_READONLY static int lfs_fs_rawgc(lfs_t *lfs) { - // Move free offset at the first unused block (lfs->lookahead.next) - // lfs->lookahead.next is equal lfs->lookahead.size when all blocks are used + // move lookahead buffer to the first unused block + // + // note we limit the lookahead buffer to at most the amount of blocks + // checkpointed, this prevents the math in lfs_alloc from underflowing lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) % lfs->block_count; + lfs->lookahead.next = 0; lfs->lookahead.size = lfs_min( 8*lfs->cfg->lookahead_size, lfs->lookahead.ckpoint); - lfs->lookahead.next = 0; // find mask of free blocks from tree memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size); @@ -648,35 +651,48 @@ static int lfs_fs_rawgc(lfs_t *lfs) { #ifndef LFS_READONLY static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { while (true) { - while (lfs->lookahead.next != lfs->lookahead.size) { - lfs_block_t off = lfs->lookahead.next; - lfs->lookahead.next += 1; - lfs->lookahead.ckpoint -= 1; - - if (!(lfs->lookahead.buffer[off / 32] & (1U << (off % 32)))) { + // scan our lookahead buffer for free blocks + while (lfs->lookahead.next < lfs->lookahead.size) { + if (!(lfs->lookahead.buffer[lfs->lookahead.next / 32] + & (1U << (lfs->lookahead.next % 32)))) { // found a free block - *block = (lfs->lookahead.start + off) % lfs->block_count; + *block = (lfs->lookahead.start + lfs->lookahead.next) + % lfs->block_count; - // eagerly find next off so an alloc ack can - // discredit old lookahead blocks - while (lfs->lookahead.next != lfs->lookahead.size && - (lfs->lookahead.buffer[lfs->lookahead.next / 32] - & (1U << (lfs->lookahead.next % 32)))) { + // eagerly find next free block to maximize how many blocks + // lfs_alloc_ckpoint makes available for scanning + while (true) { lfs->lookahead.next += 1; lfs->lookahead.ckpoint -= 1; - } - return 0; + if (lfs->lookahead.next >= lfs->lookahead.size + || !(lfs->lookahead.buffer[lfs->lookahead.next / 32] + & (1U << (lfs->lookahead.next % 32)))) { + return 0; + } + } } + + lfs->lookahead.next += 1; + lfs->lookahead.ckpoint -= 1; } - // check if we have looked at all blocks since last ack - if (lfs->lookahead.ckpoint == 0) { - LFS_ERROR("No more free space %"PRIu32, - lfs->lookahead.next + lfs->lookahead.start); + // In order to keep our block allocator from spinning forever when our + // filesystem is full, we mark points where there are no in-flight + // allocations with a checkpoint before starting a set of allocations. + // + // If we've looked at all blocks since the last checkpoint, we report + // the filesystem as out of storage. + // + if (lfs->lookahead.ckpoint <= 0) { + LFS_ERROR("No more free space 0x%"PRIx32, + (lfs->lookahead.start + lfs->lookahead.next) + % lfs->cfg->block_count); return LFS_ERR_NOSPC; } + // No blocks in our lookahead buffer, we need to scan the filesystem for + // unused blocks in the next lookahead window. int err = lfs_fs_rawgc(lfs); if(err) { return err;