mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 20:38:19 +00:00
lfs improvements
This commit is contained in:
@@ -1084,12 +1084,27 @@ void ZephyrDataStore::loadContacts(DataStoreHost *host)
|
||||
void ZephyrDataStore::saveContacts(DataStoreHost *host)
|
||||
{
|
||||
const char *path = contactsFile();
|
||||
|
||||
/* Atomic replace ONLY where there is external flash — deliberately, and
|
||||
* not to be "fixed" later.
|
||||
*
|
||||
* atomicWriteTempFile() needs room for a second full copy before the
|
||||
* rename. contacts3 is by far the largest store here (152 B per record,
|
||||
* ~47 KB at 313 contacts) and internal /lfs on these boards is 128 KB
|
||||
* total, shared with identity, prefs and channels2. Two copies would sit
|
||||
* at ~94 KB of 128 KB before LittleFS metadata, so the atomic path could
|
||||
* fail with ENOSPC exactly when it is most needed — a worse failure than
|
||||
* the one it prevents.
|
||||
*
|
||||
* channels2, identity and prefs are atomic everywhere because they are
|
||||
* small enough for the second copy to be free. Only contacts is gated.
|
||||
*
|
||||
* The non-atomic branch below is therefore the constrained-board path,
|
||||
* and it writes in place and truncates rather than unlinking first — see
|
||||
* the note there. */
|
||||
bool use_atomic = _has_ext_fs;
|
||||
const char *save_mode = use_atomic ? "atomic" : "direct";
|
||||
|
||||
if (!use_atomic && exists(path)) {
|
||||
fs_unlink(path);
|
||||
}
|
||||
|
||||
struct fs_file_t file;
|
||||
uint8_t rec[CONTACT_DATA_SZ];
|
||||
@@ -1126,6 +1141,21 @@ void ZephyrDataStore::saveContacts(DataStoreHost *host)
|
||||
};
|
||||
write_ok = atomicWriteTempFile(path, atomic_contacts_writer, &ctx, "saveContacts");
|
||||
} else {
|
||||
/* Overwrite in place, then truncate — never unlink first.
|
||||
*
|
||||
* This branch runs on boards with no external flash, i.e. the ones
|
||||
* that cannot afford the atomic temp-file dance. It used to
|
||||
* fs_unlink() the contacts file before recreating it, which left a
|
||||
* window spanning the whole ~47 KB write where contacts3 did not
|
||||
* exist at all: a power cut there lost every contact rather than
|
||||
* corrupting some. The unlink was only ever a way to truncate.
|
||||
*
|
||||
* Truncating afterwards is the same guarantee without the window —
|
||||
* the file is always present, and at worst briefly longer than its
|
||||
* new contents (stale records past the end, which the truncate then
|
||||
* removes). FS_O_TRUNC is NOT usable here: Zephyr's LittleFS
|
||||
* backend maps only CREATE/READ/WRITE/APPEND and drops TRUNC
|
||||
* silently, so asking for it would leave the stale tail in place. */
|
||||
fs_file_t_init(&file);
|
||||
int rc = fs_open(&file, path, FS_O_CREATE | FS_O_WRITE);
|
||||
if (rc < 0) {
|
||||
@@ -1133,6 +1163,20 @@ void ZephyrDataStore::saveContacts(DataStoreHost *host)
|
||||
return;
|
||||
}
|
||||
write_ok = write_contacts(&file);
|
||||
|
||||
int trunc_rc = 0;
|
||||
|
||||
if (write_ok) {
|
||||
trunc_rc = fs_truncate(&file,
|
||||
(off_t)written * CONTACT_DATA_SZ);
|
||||
if (trunc_rc < 0) {
|
||||
LOG_ERR("saveContacts: truncate to %u failed: %d",
|
||||
(unsigned)(written * CONTACT_DATA_SZ),
|
||||
trunc_rc);
|
||||
write_ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
int sync_rc = fs_sync(&file);
|
||||
fs_close(&file);
|
||||
if (!write_ok || sync_rc < 0) {
|
||||
|
||||
@@ -387,12 +387,24 @@ void CompanionMesh::onChannelAdded(ChannelDetails *)
|
||||
markChannelsDirty();
|
||||
}
|
||||
|
||||
void CompanionMesh::markContactsDirty()
|
||||
void CompanionMesh::markContactsDirty(bool substantive)
|
||||
{
|
||||
int64_t deadline = _ms->getMillis() +
|
||||
(substantive ? LAZY_WRITE_DELAY_MS
|
||||
: LAZY_WRITE_LIVENESS_MS);
|
||||
|
||||
/* Only set the timer on first dirty — don't keep pushing
|
||||
* the deadline forward or a busy mesh never flushes. */
|
||||
if (!_dirty_contacts_expiry) {
|
||||
_dirty_contacts_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
|
||||
_dirty_contacts_expiry = deadline;
|
||||
return;
|
||||
}
|
||||
|
||||
/* ...but a substantive change must not have to sit out a liveness wait
|
||||
* that is already pending. Pulling the deadline IN cannot starve the
|
||||
* flush, only hasten it. */
|
||||
if (substantive && deadline < _dirty_contacts_expiry) {
|
||||
_dirty_contacts_expiry = deadline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,8 +739,13 @@ void CompanionMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8
|
||||
LOG_INF("onDiscoveredContact: '%s' is_new=%d path_len=%d num_contacts=%d",
|
||||
contact.name, is_new, path_len, getNumContacts());
|
||||
|
||||
// Mark contacts dirty for lazy save
|
||||
markContactsDirty();
|
||||
/* A re-heard advert from a contact we already have is liveness only —
|
||||
* the base class has just refreshed last_advert_timestamp and lastmod,
|
||||
* and nothing else typically moved. Persist it lazily rather than
|
||||
* spending a full-file rewrite per advert; a genuinely new contact, a
|
||||
* path change (onContactPathUpdated) or a message all still flush on the
|
||||
* short deadline. */
|
||||
markContactsDirty(is_new);
|
||||
|
||||
// Update advert path table
|
||||
if (path && mesh::Packet::isValidPathLen(path_len)) {
|
||||
|
||||
@@ -417,9 +417,25 @@ private:
|
||||
int64_t _dirty_channels_expiry;
|
||||
static constexpr int64_t LAZY_WRITE_DELAY_MS = 5000; /* 5 seconds, matches Arduino */
|
||||
|
||||
/* Deadline for liveness-only contact updates — a re-heard advert from a
|
||||
* contact we already know, where the only fields that moved are
|
||||
* last_advert_timestamp and lastmod.
|
||||
*
|
||||
* saveContacts() rewrites the WHOLE file (fixed 152-byte records, no
|
||||
* incremental path), which on a board without external flash is ~47 KB
|
||||
* into a 128 KB LittleFS partition. Measured on a T1000-E 2026-08-24:
|
||||
* 21 full rewrites in 90 minutes, one per advert arrival, because every
|
||||
* re-advert marked the file dirty on the same 5 s deadline as a real
|
||||
* change. That is a flash-wear problem on a battery tracker.
|
||||
*
|
||||
* Liveness still persists — it just waits, so an hour of re-adverts
|
||||
* costs one write instead of fourteen. A substantive change (new
|
||||
* contact, message, path update) still pulls the deadline back in. */
|
||||
static constexpr int64_t LAZY_WRITE_LIVENESS_MS = 600000; /* 10 minutes */
|
||||
|
||||
void onLoginSent(const ContactInfo &contact) override;
|
||||
void onChannelAdded(ChannelDetails *ch) override;
|
||||
void markContactsDirty();
|
||||
void markContactsDirty(bool substantive = true);
|
||||
void markChannelsDirty();
|
||||
void flushDirtyContacts();
|
||||
void flushDirtyChannels();
|
||||
|
||||
Reference in New Issue
Block a user