Save State
OpenHCL supports the mechanism of saving & restoring state. This primitive can be used for various VM operations, but a key use case is for updating OpenHCL at runtime (a.k.a. "Servicing"). This save state can be stored in memory or on durable media, read back at a later time.
Save State First Principles
Here are the principles you must maintain when adding new save & restore code:
- Save & Restore is Forward & Backwards Compatible: A newer version of OpenHCL must understand save state from a prior version, and an older version must not crash when reading save state from a newer version.
- Do not break save state after that state is in use: Save state must be compatible from any commit to any other commit, once the product has shipped and started using that save state.1
- All Save State is Protocol Buffers: All save state is encoded as
ProtoBuf
, usingmesh
.
Best practices
- Put save state in it's own module: This makes PR reviews easier, to catch any mistakes updating save state.
- Create a unique package per crate: A logical grouping of saved state
should have the same
package
- Avoid unsupported types, when possible: These types don't support the
default values needed for safely extending save state:
- Arrays: if you need to add an array, consider a
vec
orOption<[T; N]>
instead. - Enum: if you need to add an enum, add it as `Option
' instead.
- Arrays: if you need to add an array, consider a
- Be particularly careful when updating saved state: See below.
Updating (Extending) Saved State
Since saved state is just Protocol Buffers, use the guide to updating Protocol Buffers messages as a starting point, with the following caveats:
- OpenVMM uses
sint32
to represent thei32
native type. Therefore, changingi32
tou32
is a breaking change, for example. - The Protocol Buffers docs mention what happens for newly added fields, but it
bears adding some nuance here:
- Plain
arrays
andenums
are not supported. Reading newer save state with anarray
orenum
will fail on an older build. Instead, wrap these in anOption
. - Old -> New Save State: Save state from a prior revision will not contain
some newly added fields. Those fields will get the default
values. This is
how that breaks down for the rust types:
Option<T>
=>None
- Structs => each field gets that field's default value
- Vecs => empty vec
- Numbers => 0
- Strings =>
""
- New -> Old Save State: Unknown fields are ignored.
- Plain
- Ensure that default values for new saved state fields make semantic sense.
Here is the heuristic: if you need a conditional to check if the restored
value is the default because it was not included in save state, you should
use an
Option
. If you wouldn't otherwise need a conditional, you should not use anOption
. - Define your saved state so that the natural default of
bool
types isfalse
.
Defining Saved State
Saved state is defined as a struct
that has #[derive(Protobuf)]
and
#[mesh(package = "package_name")]
attributes. Here is an example, taken from
the nvme_driver
:
#![allow(unused)] fn main() { pub mod save_restore { use super::*; /// Save/restore state for IoQueue. #[derive(Protobuf, Clone, Debug)] #[mesh(package = "nvme_driver")] pub struct IoQueueSavedState { #[mesh(1)] /// Which CPU handles requests. pub cpu: u32, #[mesh(2)] /// Interrupt vector (MSI-X) pub iv: u32, #[mesh(3)] pub queue_data: QueuePairSavedState, } } }
Saved state is in use when it reaches a release branch that is in tell mode. See release management for details.