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
@@ -50,6 +50,12 @@ class ContactCacheTest(unittest.TestCase):
|
||||
def test_esp32_disabled_contact_recovery_is_detected(self):
|
||||
self.run_cache_test("ESP32_PLATFORM", disable_recovery=True)
|
||||
|
||||
def test_esp32_unconditional_contact_reset_is_detected(self):
|
||||
self.run_cache_test("ESP32_PLATFORM", disable_raw_recovery=True)
|
||||
|
||||
def test_nrf52_unconditional_contact_reset_is_detected(self):
|
||||
self.run_cache_test("NRF52_PLATFORM", disable_raw_recovery=True)
|
||||
|
||||
def test_nrf52_unsafe_legacy_discovery_recovery_is_detected(self):
|
||||
self.run_cache_test("NRF52_PLATFORM", unsafe_legacy_discovery=True)
|
||||
|
||||
@@ -57,6 +63,7 @@ class ContactCacheTest(unittest.TestCase):
|
||||
self.run_cache_test("NRF52_PLATFORM", unsafe_page_discovery=True)
|
||||
|
||||
def run_cache_test(self, platform, disable_crc_batch=False, disable_recovery=False,
|
||||
disable_raw_recovery=False,
|
||||
unsafe_legacy_discovery=False, unsafe_page_discovery=False):
|
||||
with tempfile.TemporaryDirectory(prefix="mesh-contact-cache-") as temp:
|
||||
temp = Path(temp)
|
||||
@@ -119,6 +126,12 @@ class ContactCacheTest(unittest.TestCase):
|
||||
self.assertIn(recovery, implementation)
|
||||
implementation = implementation.replace(
|
||||
recovery, "void DataStore::recoverContacts(DataStoreHost*) {}")
|
||||
if disable_raw_recovery:
|
||||
# Restore the exact pre-fix unconditional host-reset path,
|
||||
# leaving fault classification and bounded fallback unchanged.
|
||||
raw_retry = method(store, "if (_contact_recovery & ContactRecoveryRawOnly) {")
|
||||
self.assertEqual(implementation.count(raw_retry), 1)
|
||||
implementation = implementation.replace(raw_retry, "")
|
||||
if unsafe_legacy_discovery:
|
||||
discovery = method(store,
|
||||
'if (!contactPathPresence(contacts_fs, "/contacts3", legacy_exists))')
|
||||
@@ -182,7 +195,11 @@ class ContactCacheTest(unittest.TestCase):
|
||||
], capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
result = subprocess.run([str(binary)], capture_output=True, text=True)
|
||||
if unsafe_page_discovery:
|
||||
if disable_raw_recovery:
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("raw_read_recovery_preserves_accepted_ram_state", result.stderr)
|
||||
self.assertIn("f.host.recovery_resets == 0", result.stderr)
|
||||
elif unsafe_page_discovery:
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("nrf52_unknown_page_stat_preserves_contact_authority", result.stderr)
|
||||
self.assertIn("f.host.contacts.size()", result.stderr)
|
||||
|
||||
Reference in new issue
Block a user