From cdfddfad501b4c6f19c311c411a303730b77a7bf Mon Sep 17 00:00:00 2001 From: Gene Michael Lauria Date: Thu, 17 Sep 2026 10:01:44 -0700 Subject: [PATCH] FileStore: close the active segment before threshold compaction compact_if_threshold() called compact() with active_file still open. rotate_segment_if_needed() closes it first, but the threshold path did not, so on ESP-IDF LittleFS every threshold-triggered compaction failed to delete the active segment: E esp_littlefs: Failed to unlink path "./hashlist_store/seg1.dat". Has open FD. Plain POSIX allows unlinking an open file, which is why this never showed up off-device. Flush and close active_file before compact(), and reopen the current segment if compaction fails or is skipped so appends continue. Seen on microReticulum_Firmware (Heltec V4) relaying link traffic, where the 100-record packet hashlist hits the 25% dead threshold every ~25 packets. Co-Authored-By: Claude Fable 5.1 --- include/microStore/FileStore.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/microStore/FileStore.h b/include/microStore/FileStore.h index c243e21..90d1051 100644 --- a/include/microStore/FileStore.h +++ b/include/microStore/FileStore.h @@ -1267,12 +1267,22 @@ USTORE_LOG("[ustore] rotate_segment_if_needed: closing active file\n"); //if (total > 0 && _dead_since_compact * 100 / total >= USTORE_COMPACT_THRESHOLD) { if (policy_max_recs > 0 && _dead_since_compact * 100 / policy_max_recs >= USTORE_COMPACT_THRESHOLD) { USTORE_LOG("[ustore] Compaction triggered by deleted threshold (dead=%u, max=%u)\n", _dead_since_compact, policy_max_recs); + // The active segment must be closed before compact() deletes it, as + // rotate_segment_if_needed() already does. ESP-IDF LittleFS refuses to + // unlink an open file ("Failed to unlink ... Has open FD"), which left + // the active segment in place after every threshold compaction. + flush_buffer(); + if (active_file) active_file.close(); if (compact()) { // After threshold-triggered compaction, seg0 holds the compacted data. // Open seg1 for new writes, mirroring what rotate_segment_if_needed() does // after a rotation-triggered compaction. current_segment = 1; open_segment(current_segment); + } else { + // Compaction failed or was skipped; resume appending to the segment + // that was active before. + open_segment(current_segment); } } #endif