Skip to main content

StorageList

Struct StorageList 

Source
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:

  1. The StorageListNodes, which each store one configuration item
  2. 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>

Source

pub const fn new() -> Self

const constructor to make a new empty list. Intended to be used to create a static.

Will panic if KEPT_RECORDS is 0.

static GLOBAL_LIST: StorageList<CriticalSectionRawMutex, 3> = StorageList::new();
Source

pub const fn try_new() -> Option<Self>

const constructor to make a new empty list. Intended to be used to create a static.

Will return None if KEPT_RECORDS is 0.

static GLOBAL_LIST: StorageList<CriticalSectionRawMutex, 3> = StorageList::try_new().unwrap();
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.

Source

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:
Source

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:

  1. Locks the mutex
  2. Iterates through each node currently linked in the list
  3. Serializes each node and writes it to flash
  4. Verifies the written data matches what was intended to be written
  5. On success, marks all nodes as no longer having pending changes
  6. 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: a NdlDataStorage backend that the list should be written to
  • read_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 length S::MAX_ELEM_SIZE.
Source

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.

Source

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.

Source

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.

Source

pub async fn reset_cache(&self)

Manually reset the cfg-noodle cache

This should not normally be necessary, and has some important caveats:

  1. The next process_reads will be slow (requires re-building cache, like on a typical first process_reads), and a process_reads MUST be completed before calling process_writes/process_garbage, similar to after a clean startup.
  2. This method does NOT reset the cache of our storage, for example if sequential-storage is used with a key/pointer cache. You still must manually perform this action separately.
  3. 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:

  1. process_reads succeeds at startup
  2. Some later failure/corruption of the underlying storage makes it necessary to wipe or reset the underlying storage method
  3. 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:

  1. Notice the state is bad (failed writes? some other signs?)
  2. Do whatever reset/full wipe/reformat of the external storage
  3. (if necessary), reset the cache of the external storage (e.g. s-s’s storage cache)
  4. Call this method
  5. Call process_reads() to re-build the cache
  6. Call process_garbage(), if necessary (probably not if we have an empty storage medium!)
  7. 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.

Trait Implementations§

Source§

impl<R: ScopedRawMutex + ConstInit, const KEPT_RECORDS: usize> Default for StorageList<R, KEPT_RECORDS>

Source§

fn default() -> Self

this only exists to shut up the clippy lint about impl’ing default

Auto Trait Implementations§

§

impl<R, const KEPT_RECORDS: usize = 3> !Freeze for StorageList<R, KEPT_RECORDS>

§

impl<R, const KEPT_RECORDS: usize = 3> !RefUnwindSafe for StorageList<R, KEPT_RECORDS>

§

impl<R, const KEPT_RECORDS: usize = 3> !UnwindSafe for StorageList<R, KEPT_RECORDS>

§

impl<R, const KEPT_RECORDS: usize> Send for StorageList<R, KEPT_RECORDS>
where R: Send,

§

impl<R, const KEPT_RECORDS: usize> Sync for StorageList<R, KEPT_RECORDS>
where R: Sync,

§

impl<R, const KEPT_RECORDS: usize> Unpin for StorageList<R, KEPT_RECORDS>
where R: Unpin,

§

impl<R, const KEPT_RECORDS: usize> UnsafeUnpin for StorageList<R, KEPT_RECORDS>
where R: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.