Preserve accepted contacts after transient path read errors
Recheck cached immutable paths before resetting the live table after raw I/O failure. Keep accepted RAM mutations, current jobs and save deadlines when reads recover; stronger source or publication uncertainty still forces full recovery. Use the existing recovery byte and a fixed scratch buffer, with no cache allocation or eviction. Cover full-cache transient recovery, persistent I/O and checksum failures, nRF unread-scope restoration, active ESP jobs, real uncertain publication and unchanged lazy deadlines with sanitizer tests and restored-reset negative controls.
This commit is contained in:
1 parent
a3e9bec997
commit
587700a36a
7 files changed
+339
-9
No files matched your search
@@ -2194,7 +2194,35 @@ bool DataStore::prepareContactSave() {
|
||||
#endif
|
||||
|
||||
void DataStore::recoverContacts(DataStoreHost* host) {
|
||||
if (!contactRecoveryPending() || !host->resetContactsForRecovery()) return;
|
||||
if (!contactRecoveryPending()) return;
|
||||
#if MESH_CONTACT_CACHE
|
||||
if (_contact_recovery & ContactRecoveryRawOnly) {
|
||||
// A failed cold read did not change names/generations. Retry immutable
|
||||
// current paths before retiring accepted, not-yet-persisted RAM changes.
|
||||
const uint8_t previous = _contact_recovery;
|
||||
_contact_recovery &= ~(ContactRecoveryPending | ContactRecoveryRawOnly
|
||||
| ContactRecoveryLegacy);
|
||||
#if defined(NRF52_PLATFORM)
|
||||
const auto unread = _unread_contact_pages;
|
||||
_unread_contact_pages.clearAll();
|
||||
#endif
|
||||
uint8_t path[64];
|
||||
bool readable = true;
|
||||
for (uint32_t i = 0;; ++i) {
|
||||
const ContactInfo* contact = host->getContactForStore(i);
|
||||
if (contact == nullptr) break;
|
||||
// copyPathTo uses checksum-checked read(), not allocating/evicting view().
|
||||
if (!contact->copyPathTo(path)) { readable = false; break; }
|
||||
}
|
||||
if (readable && !contactRecoveryPending()) return;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
for (uint8_t page = 0; page < mesh::storage::CONTACT_PAGE_COUNT; ++page)
|
||||
if (unread.bits() & (1UL << page)) _unread_contact_pages.mark(page);
|
||||
#endif
|
||||
queueContactRecovery((previous & ContactRecoveryLegacy) != 0);
|
||||
}
|
||||
#endif
|
||||
if (!host->resetContactsForRecovery()) return;
|
||||
#if defined(ESP32_PLATFORM)
|
||||
cancelContactWrite();
|
||||
_contact_write_requested = false;
|
||||
@@ -4590,7 +4618,7 @@ bool DataStore::readStoredPath(uint16_t source, uint8_t path[64]) {
|
||||
&& file.seek(offset + 76) && file.read(path, 64) == 64;
|
||||
if (!ok) {
|
||||
file.close();
|
||||
queueContactRecovery(true);
|
||||
queueContactReadRecovery(true);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -4615,9 +4643,9 @@ bool DataStore::readStoredPath(uint16_t source, uint8_t path[64]) {
|
||||
if (source & mesh::ContactPathStorage::PAGED)
|
||||
_unread_contact_pages.mark((source & ~mesh::ContactPathStorage::PAGED)
|
||||
/ mesh::storage::CONTACTS_PER_PAGE);
|
||||
queueContactRecovery((source & mesh::ContactPathStorage::PAGED) == 0);
|
||||
queueContactReadRecovery((source & mesh::ContactPathStorage::PAGED) == 0);
|
||||
#else
|
||||
queueContactRecovery(true);
|
||||
queueContactReadRecovery(true);
|
||||
#endif
|
||||
}
|
||||
// The pool checks the CRC captured when this immutable path value was bound.
|
||||
|
||||
@@ -50,12 +50,28 @@ class DataStore
|
||||
// permission to discard contacts. No recovery files or boot counters needed.
|
||||
enum : uint8_t { ContactRecoveryPending = 1, ContactRecoveryLegacy = 2,
|
||||
ContactRecoveryBackup = 4, ContactStorageUnavailable = 8,
|
||||
ContactMigrationDeferred = 16 };
|
||||
ContactMigrationDeferred = 16,
|
||||
#if MESH_CONTACT_CACHE
|
||||
ContactRecoveryRawOnly = 32,
|
||||
#endif
|
||||
};
|
||||
uint8_t _contact_recovery = 0;
|
||||
void queueContactRecovery(bool legacy = false) {
|
||||
#if MESH_CONTACT_CACHE
|
||||
// Source/publication uncertainty always supersedes a raw-read retry.
|
||||
_contact_recovery &= ~ContactRecoveryRawOnly;
|
||||
#endif
|
||||
_contact_recovery |= ContactRecoveryPending
|
||||
| (legacy ? ContactRecoveryLegacy : 0);
|
||||
}
|
||||
#if MESH_CONTACT_CACHE
|
||||
void queueContactReadRecovery(bool legacy = false) {
|
||||
if (!(_contact_recovery & ContactRecoveryPending))
|
||||
_contact_recovery |= ContactRecoveryRawOnly;
|
||||
_contact_recovery |= ContactRecoveryPending
|
||||
| (legacy ? ContactRecoveryLegacy : 0);
|
||||
}
|
||||
#endif
|
||||
void clearContactLoadFailure();
|
||||
#if !defined(NRF52_PLATFORM)
|
||||
const char* selectContactFile(bool& present);
|
||||
|
||||
@@ -11352,7 +11352,7 @@ void MyMesh::loop() {
|
||||
if (_store->contactRecoveryPending() && !mesh::morseTimingActive(_radio)) {
|
||||
_store->recoverContacts(this);
|
||||
updateGpsTelemetryPolicy();
|
||||
if (_store->hasPendingContactWrites()) {
|
||||
if (!dirty_contacts_expiry && _store->hasPendingContactWrites()) {
|
||||
dirty_contacts_expiry = mesh::nonzeroLazyPersistenceDeadline(
|
||||
futureMillis(LAZY_CONTACTS_WRITE_DELAY));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user