pub struct StorageList<R: ScopedRawMutex, const KEPT_RECORDS: usize = 3> { /* private fields */ }Expand description
“Global anchor” of all storage items.
It serves as the meeting point between two conceptual pieces:
- The
StorageListNodes, which each store one configuration item - The worker task that owns the external flash, and serves the role of loading data FROM flash (and putting it in the Nodes), as well as the role of deciding when to write data TO flash (retrieving it from each node).
Implementations§
Source§impl<R: ScopedRawMutex + ConstInit, const KEPT_RECORDS: usize> StorageList<R, KEPT_RECORDS>
impl<R: ScopedRawMutex + ConstInit, const KEPT_RECORDS: usize> StorageList<R, KEPT_RECORDS>
Source§impl<R: ScopedRawMutex, const KEPT_RECORDS: usize> StorageList<R, KEPT_RECORDS>
These are public methods for the StorageList. They currently are intended to be
used by the “storage worker task”, that decides when we actually want to
interact with the flash.
impl<R: ScopedRawMutex, const KEPT_RECORDS: usize> StorageList<R, KEPT_RECORDS>
These are public methods for the StorageList. They currently are intended to be
used by the “storage worker task”, that decides when we actually want to
interact with the flash.
Sourcepub async fn process_reads<S: NdlDataStorage>(
&'static self,
storage: &mut S,
buf: &mut [u8],
) -> Result<ProcessReadCounters, LoadStoreError<S::Error>>
pub async fn process_reads<S: NdlDataStorage>( &'static self, storage: &mut S, buf: &mut [u8], ) -> Result<ProcessReadCounters, LoadStoreError<S::Error>>
Process any nodes that are requesting flash data
This function will traverse the list in order to attempt to fill any StorageListNodes that
are still in the State::Initial state, meaning they are waiting to see if the
flash contains the data they are interested or not.
The need for this is signalled by calling Self::needs_read() and awaiting the returned WaitQueue
signal. You may choose to “debounce” this and wait for some time after the signal is noted, in order
to batch up as many nodes as possible to reduce the amount of loading from external storage.
Reads are ONLY necessary after new nodes are attached with StorageListNode::attach(), which will
only happen for some amount of time after startup.
This function will hold an async mutex for the duration of access, inhibiting other operations such
as Self::process_writes(). Nodes that have already had their data populated will not have read
access inhibited, though calls to StorageListNodeHandle::write() WILL not resolve until this
operation completes.
§Params:
storage: aNdlDataStorageobject containing the necessary information to callNdlDataStorage::pushbuf: a scratch-buffer of lengthS::MAX_ELEM_SIZE.
Sourcepub async fn process_writes<S: NdlDataStorage>(
&'static self,
storage: &mut S,
buf: &mut [u8],
) -> Result<ProcessWriteCounters, LoadStoreError<S::Error>>
pub async fn process_writes<S: NdlDataStorage>( &'static self, storage: &mut S, buf: &mut [u8], ) -> Result<ProcessWriteCounters, LoadStoreError<S::Error>>
Process writes to flash.
If any of the nodes attached to the list has pending writes, this function writes the ENTIRE list to flash. This method:
- Locks the mutex
- Iterates through each node currently linked in the list
- Serializes each node and writes it to flash
- Verifies the written data matches what was intended to be written
- On success, marks all nodes as no longer having pending changes
- Releases the mutex
The need for this call can be determined by calling Self::needs_write(), and awaiting
the returned WaitQueue. You may choose to debounce or delay calling process_writes
in order to reduce the number of writes/erase to the flash, however data is not “synced”
to disk until a call to process_writes completes successfully.
This method WILL NOT succeed until after the first call to process_read.
§Arguments:
storage: aNdlDataStoragebackend that the list should be written toread_buf: a scratch buffer used to store data read from flash during verification. Must be able to hold any serialized node.buf: a scratch-buffer of lengthS::MAX_ELEM_SIZE.
Sourcepub async fn process_garbage<S: NdlDataStorage>(
&'static self,
storage: &mut S,
buf: &mut [u8],
) -> Result<ProcessGarbageCounters, LoadStoreError<S::Error>>
pub async fn process_garbage<S: NdlDataStorage>( &'static self, storage: &mut S, buf: &mut [u8], ) -> Result<ProcessGarbageCounters, LoadStoreError<S::Error>>
Process garbage collection
Note: this process may take a while, but does NOT hold the mutex for the entire time. However, attaches and writes will not be processed until after completion.
Although we don’t HOLD the mutex for the entire time, by nature of the fact
that we have exclusive access to the storage medium, we can be fairly confident
it would not be possible to have some other function like process_reads or
process_writes called, even while we are not holding the mutex.
Sourcepub async fn needs_read(&self)
pub async fn needs_read(&self)
Returns a reference to the wait queue that signals when nodes need to be read from flash. This queue is woken when new nodes are attached and require data to be loaded.
Sourcepub async fn needs_write(&self)
pub async fn needs_write(&self)
Returns a reference to the wait queue that signals when nodes have pending writes to flash. This queue is woken when node data is modified and needs to be persisted.
Sourcepub async fn reset_cache(&self)
pub async fn reset_cache(&self)
Manually reset the cfg-noodle cache
This should not normally be necessary, and has some important caveats:
- The next
process_readswill be slow (requires re-building cache, like on a typical firstprocess_reads), and aprocess_readsMUST be completed before callingprocess_writes/process_garbage, similar to after a clean startup. - This method does NOT reset the cache of our storage, for example if
sequential-storageis used with a key/pointer cache. You still must manually perform this action separately. - This method does NOT reset any of the [
StorageNode]s. If they have previously loaded some value, they will retain that value moving forward.
This method is intended for the EXTREMELY RARE case where:
process_readssucceeds at startup- Some later failure/corruption of the underlying storage makes it necessary to wipe or reset the underlying storage method
- We need to “fix the plane while it is flying”, and try to get cfg-noodle back into a reasonable state.
In this dangerous case, you probably want to do something like the following from the I/O worker task:
- Notice the state is bad (failed writes? some other signs?)
- Do whatever reset/full wipe/reformat of the external storage
- (if necessary), reset the cache of the external storage (e.g. s-s’s storage cache)
- Call this method
- Call
process_reads()to re-build the cache - Call
process_garbage(), if necessary (probably not if we have an empty storage medium!) - Call
process_writes()to persist the current state of in-memory data back to the external flash
This method does not perform any I/O.