Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/microStore/FileStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,11 @@ USTORE_LOG("[ustore] Moving temporary file to: %s\n", seg0);
bool compact()
{
USTORE_LOG("[ustore] Compacting storage...\n");
// The active segment is one of the sources removed below. LittleFS
// and FatFS refuse to unlink a file that is still open (POSIX would
// allow it), so close it first. Both exit paths reopen a segment so
// the store stays writable for callers that invoke compact() directly.
if (active_file) active_file.close();

// --- Phase 1: write COMPACTING journal (next_seg=0: no source segments deleted yet) ---
write_journal(JOURNAL_COMPACTING, 0, 0);
Expand Down Expand Up @@ -1542,6 +1547,7 @@ USTORE_LOG("[ustore] Closing tmp file: %s\n", tmp_path);
outf.close();

if (!write_ok) {
open_segment(current_segment); // keep the store writable after a failed compaction
if (committed_segs == 0) {
// No source segments were deleted — safe to discard compact.tmp entirely.
USTORE_LOG("[ustore] WARNING: Compact aborted: storage full, all segments preserved\n");
Expand All @@ -1561,6 +1567,7 @@ USTORE_LOG("[ustore] Closing tmp file: %s\n", tmp_path);
write_journal(JOURNAL_COMMIT);

finalize_compaction(); // rename + index rebuild
open_segment(current_segment); // seg0 = compacted data; internal callers move on to seg1

clear_journal();

Expand Down
34 changes: 34 additions & 0 deletions test/test_file_store/test_file_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,39 @@ void test_file_store_compact_basic() {
TEST_ASSERT_EQUAL(0, memcmp(buf, "v3", 2));
}

// A direct compact() call must leave the store writable. The active segment is
// one of the source segments compaction deletes, so compact() has to close it
// (LittleFS/FatFS refuse to unlink an open file) and reopen a segment before
// returning; otherwise records written afterwards go to a stale handle and are
// lost on reload.
void test_file_store_compact_keeps_store_writable() {
reset_ram_fs();

uint32_t now = microStore::time();
{
microStore::FileStore store;
auto fs = make_ram_fs();
store.init(fs, "/p");
store.put("a", "v1", /*ttl=*/0, now);
store.put("b", "v2", /*ttl=*/0, now);
store.remove("b");
TEST_ASSERT_TRUE(store.compact());
store.put("c", "v3", /*ttl=*/0, now); // written after compaction
TEST_ASSERT_TRUE(store.exists("c"));
}
remove_ram_file("/p_index.dat"); // force a rebuild from the segment files

microStore::FileStore reader;
auto fs = make_ram_fs();
reader.init(fs, "/p");

TEST_ASSERT_EQUAL(2u, reader.size()); // a(v1) and c(v3)
uint8_t buf[32]; uint16_t sz = sizeof(buf);
TEST_ASSERT_TRUE(reader.get("c", buf, &sz));
TEST_ASSERT_EQUAL(2u, sz);
TEST_ASSERT_EQUAL(0, memcmp(buf, "v3", 2));
}

/* ===========================================================================
* Directory-mode prefix tests
*
Expand Down Expand Up @@ -800,6 +833,7 @@ int runUnityTests(void) {
// Additional edge-case tests
RUN_TEST(test_file_store_ttl_exists_expires);
RUN_TEST(test_file_store_compact_basic);
RUN_TEST(test_file_store_compact_keeps_store_writable);
// Directory-mode prefix
RUN_TEST(test_dir_prefix_segment_and_index_names);
RUN_TEST(test_legacy_prefix_unchanged);
Expand Down