Summary
After two interrupted initial stores, a later uninterrupted MapStorage::store_item can return Ok(()), but a freshly reconstructed MapStorage cannot fetch the item and returns None.
I reproduced this directly on master at d10f0d517926c9b2303a1375f0da653443dec56a with the crate's own MockFlashBase; no external adapter is involved.
Minimal sequence
The flash geometry is two 128-byte pages with one-byte words, an uncached MapStorage<u8, ...>, key 0, and an eight-byte value.
- Set
bytes_until_shutoff = Some(7) and attempt the initial store. It returns EarlyShutoff.
- Destroy and reconstruct
MapStorage; fetching key 0 returns None.
- Repeat steps 1–2 once.
- Restore power and store a different value.
store_item returns Ok(()).
- Destroy and reconstruct
MapStorage again.
- Fetching key
0 returns None, despite the successful clean store.
Cause
ItemHeaderIter advances one flash word when it encounters a corrupted/torn header. find_next_free_item_spot then appends at the first erased byte following that header. Bytes from the new header can complete a valid-looking phantom header that starts inside the torn header and overlaps the new item. Traversal follows the phantom record and skips the actual item.
Expected behavior
An uninterrupted store_item that returns Ok(()) must remain visible after reconstructing the storage. If the flash state cannot be recovered safely, the store should return an error instead of acknowledging a non-durable item.
The regression test, a fix that rotates away from a page when the append scan encountered a corrupted header, and a new repeated power-cut/reboot fuzz target are submitted in #137.
Summary
After two interrupted initial stores, a later uninterrupted
MapStorage::store_itemcan returnOk(()), but a freshly reconstructedMapStoragecannot fetch the item and returnsNone.I reproduced this directly on
masteratd10f0d517926c9b2303a1375f0da653443dec56awith the crate's ownMockFlashBase; no external adapter is involved.Minimal sequence
The flash geometry is two 128-byte pages with one-byte words, an uncached
MapStorage<u8, ...>, key0, and an eight-byte value.bytes_until_shutoff = Some(7)and attempt the initial store. It returnsEarlyShutoff.MapStorage; fetching key0returnsNone.store_itemreturnsOk(()).MapStorageagain.0returnsNone, despite the successful clean store.Cause
ItemHeaderIteradvances one flash word when it encounters a corrupted/torn header.find_next_free_item_spotthen appends at the first erased byte following that header. Bytes from the new header can complete a valid-looking phantom header that starts inside the torn header and overlaps the new item. Traversal follows the phantom record and skips the actual item.Expected behavior
An uninterrupted
store_itemthat returnsOk(())must remain visible after reconstructing the storage. If the flash state cannot be recovered safely, the store should return an error instead of acknowledging a non-durable item.The regression test, a fix that rotates away from a page when the append scan encountered a corrupted header, and a new repeated power-cut/reboot fuzz target are submitted in #137.