Skip to content

Commit 66b47e7

Browse files
committed
[host] migrate to snapshot-based sandbox API
Replace transition-based sandbox evolution with direct snapshot/restore API: - Remove EvolvableSandbox/DevolvableSandbox trait usage - Replace transition callbacks with direct method calls - Add snapshot/restore methods to LoadedWasmSandbox - Store initial snapshots in WasmSandbox for efficient unloading - Export Snapshot type in public API This provides more direct control over sandbox state management and enables features like checkpoint/restore. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 19f771e commit 66b47e7

File tree

5 files changed

+172
-118
lines changed

5 files changed

+172
-118
lines changed

src/hyperlight_wasm/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub type Result<T> = hyperlight_host::Result<T>;
3939
pub use hyperlight_host::is_hypervisor_present;
4040
/// Create a generic HyperlightError
4141
pub use hyperlight_host::new_error;
42+
// A snapshot of the memory of a sandbox at a given point in time.
43+
pub use hyperlight_host::sandbox::snapshot::Snapshot;
4244

4345
/// Get the build information for this version of hyperlight-wasm
4446
pub fn get_build_info() -> BuildInfo {

src/hyperlight_wasm/src/sandbox/loaded_wasm_sandbox.rs

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use std::fmt::Debug;
1718
use std::sync::Arc;
1819

1920
use hyperlight_host::func::{ParameterTuple, SupportedReturnType};
2021
// re-export the InterruptHandle trait as it's part of the public API
2122
pub use hyperlight_host::hypervisor::InterruptHandle;
2223
use hyperlight_host::sandbox::Callable;
23-
use hyperlight_host::sandbox_state::sandbox::{DevolvableSandbox, Sandbox};
24-
use hyperlight_host::sandbox_state::transition::Noop;
24+
use hyperlight_host::sandbox::snapshot::Snapshot;
2525
use hyperlight_host::{MultiUseSandbox, Result, log_then_return, new_error};
2626

2727
use super::metrics::METRIC_TOTAL_LOADED_WASM_SANDBOXES;
@@ -37,16 +37,15 @@ use crate::sandbox::metrics::{METRIC_ACTIVE_LOADED_WASM_SANDBOXES, METRIC_SANDBO
3737
/// memory context. If you want to "reset" the memory context, create
3838
/// a new `LoadedWasmSandbox` -- either from another `WasmSandbox` or by
3939
/// calling `my_loaded_wasm_sandbox.devolve()?.evolve()?`
40-
#[derive(Debug)]
4140
pub struct LoadedWasmSandbox {
4241
// inner is an Option<MultiUseSandbox> as we need to take ownership of it
4342
// We implement drop on the LoadedWasmSandbox to decrement the count of Sandboxes when it is dropped
4443
// because of this we cannot implement drop without making inner an Option (alternatively we could make MultiUseSandbox Copy but that would introduce other issues)
4544
inner: Option<MultiUseSandbox>,
45+
// The state the sandbox was in before loading a wasm module. Used for transitioning back to a `WasmSandbox` (unloading the wasm module).
46+
runtime_snapshot: Option<Snapshot>,
4647
}
4748

48-
impl Sandbox for LoadedWasmSandbox {}
49-
5049
impl LoadedWasmSandbox {
5150
/// Call the function in the guest with the name `fn_name`, passing
5251
/// parameters `params`.
@@ -64,17 +63,50 @@ impl LoadedWasmSandbox {
6463
None => log_then_return!("No inner MultiUseSandbox to call_guest_function"),
6564
}
6665
}
66+
67+
/// Take a snapshot of the current state of the sandbox.
68+
pub fn snapshot(&mut self) -> Result<Snapshot> {
69+
match &mut self.inner {
70+
Some(inner) => inner.snapshot(),
71+
None => log_then_return!("No inner MultiUseSandbox to snapshot"),
72+
}
73+
}
74+
75+
/// Restore the state of the sandbox to the state captured in the given snapshot.
76+
pub fn restore(&mut self, snapshot: &Snapshot) -> Result<()> {
77+
match &mut self.inner {
78+
Some(inner) => inner.restore(snapshot),
79+
None => log_then_return!("No inner MultiUseSandbox to restore"),
80+
}
81+
}
82+
6783
/// unload the wasm module and return a `WasmSandbox` that can be used to load another module
68-
pub fn unload_module(self) -> Result<WasmSandbox> {
69-
self.devolve(Noop::default()).inspect(|_| {
84+
pub fn unload_module(mut self) -> Result<WasmSandbox> {
85+
let sandbox = self
86+
.inner
87+
.take()
88+
.ok_or_else(|| new_error!("No inner MultiUseSandbox to unload"))?;
89+
90+
let snapshot = self
91+
.runtime_snapshot
92+
.take()
93+
.ok_or_else(|| new_error!("No snapshot of the WasmSandbox to unload"))?;
94+
95+
WasmSandbox::new_from_loaded(sandbox, snapshot).inspect(|_| {
7096
metrics::counter!(METRIC_SANDBOX_UNLOADS).increment(1);
7197
})
7298
}
7399

74-
pub(super) fn new(inner: MultiUseSandbox) -> Result<LoadedWasmSandbox> {
100+
pub(super) fn new(
101+
inner: MultiUseSandbox,
102+
runtime_snapshot: Snapshot,
103+
) -> Result<LoadedWasmSandbox> {
75104
metrics::gauge!(METRIC_ACTIVE_LOADED_WASM_SANDBOXES).increment(1);
76105
metrics::counter!(METRIC_TOTAL_LOADED_WASM_SANDBOXES).increment(1);
77-
Ok(LoadedWasmSandbox { inner: Some(inner) })
106+
Ok(LoadedWasmSandbox {
107+
inner: Some(inner),
108+
runtime_snapshot: Some(runtime_snapshot),
109+
})
78110
}
79111

80112
/// Get a handle to the interrupt handler for this sandbox,
@@ -100,26 +132,20 @@ impl Callable for LoadedWasmSandbox {
100132
}
101133
}
102134

103-
/// Capability to transform a `LoadedWasmSandbox` back down to a
104-
/// `WasmSandbox`
105-
impl DevolvableSandbox<LoadedWasmSandbox, WasmSandbox, Noop<LoadedWasmSandbox, WasmSandbox>>
106-
for LoadedWasmSandbox
107-
{
108-
fn devolve(mut self, _: Noop<LoadedWasmSandbox, WasmSandbox>) -> Result<WasmSandbox> {
109-
let new_inner: MultiUseSandbox = match self.inner.take() {
110-
Some(inner) => inner.devolve(Noop::default())?,
111-
None => log_then_return!("No inner MultiUseSandbox to devolve"),
112-
};
113-
Ok(WasmSandbox::new(new_inner))
114-
}
115-
}
116-
117135
impl Drop for LoadedWasmSandbox {
118136
fn drop(&mut self) {
119137
metrics::gauge!(METRIC_ACTIVE_LOADED_WASM_SANDBOXES).decrement(1);
120138
}
121139
}
122140

141+
impl Debug for LoadedWasmSandbox {
142+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143+
f.debug_struct("LoadedWasmSandbox")
144+
.field("inner", &self.inner)
145+
.finish()
146+
}
147+
}
148+
123149
#[cfg(test)]
124150
mod tests {
125151
use std::sync::Arc;
@@ -161,7 +187,7 @@ mod tests {
161187
}
162188
.unwrap();
163189

164-
call_funcs(loaded_wasm_sandbox, 1000);
190+
call_funcs(loaded_wasm_sandbox, 500);
165191
}
166192

167193
#[test]

src/hyperlight_wasm/src/sandbox/metrics.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,16 @@ pub(crate) static METRIC_SANDBOX_UNLOADS: &str = "sandbox_unloads_total";
3535
#[cfg(test)]
3636
mod tests {
3737
use examples_common::get_wasm_module_path;
38+
use hyperlight_host::HyperlightError;
3839

39-
use crate::{LoadedWasmSandbox, ProtoWasmSandbox};
40+
use crate::{LoadedWasmSandbox, ProtoWasmSandbox, Result};
41+
42+
fn get_time_since_boot_microsecond() -> Result<i64> {
43+
let res = std::time::SystemTime::now()
44+
.duration_since(std::time::SystemTime::UNIX_EPOCH)?
45+
.as_micros();
46+
i64::try_from(res).map_err(HyperlightError::IntConversionFailure)
47+
}
4048

4149
#[test]
4250
#[ignore = "Needs to run separately to not get influenced by other tests"]
@@ -46,7 +54,13 @@ mod tests {
4654
recorder.install().unwrap();
4755

4856
let snapshot = {
49-
let sandbox = ProtoWasmSandbox::default();
57+
let mut sandbox = ProtoWasmSandbox::default();
58+
sandbox
59+
.register(
60+
"GetTimeSinceBootMicrosecond",
61+
get_time_since_boot_microsecond,
62+
)
63+
.unwrap();
5064

5165
let wasm_sandbox = sandbox.load_runtime().unwrap();
5266
let loaded_wasm_sandbox: LoadedWasmSandbox = {
@@ -57,6 +71,10 @@ mod tests {
5771
snapshotter.snapshot()
5872
};
5973
let snapshot = snapshot.into_vec();
60-
assert_eq!(snapshot.len(), 8);
74+
if cfg!(feature = "function_call_metrics") {
75+
assert_eq!(snapshot.len(), 10);
76+
} else {
77+
assert_eq!(snapshot.len(), 8);
78+
}
6179
}
6280
}

src/hyperlight_wasm/src/sandbox/proto_wasm_sandbox.rs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,11 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
use hyperlight_host::func::call_ctx::MultiUseGuestCallContext;
1817
use hyperlight_host::func::{HostFunction, ParameterTuple, Registerable, SupportedReturnType};
19-
use hyperlight_host::sandbox::Callable;
2018
#[cfg(all(feature = "seccomp", target_os = "linux"))]
2119
use hyperlight_host::sandbox::ExtraAllowedSyscall;
2220
use hyperlight_host::sandbox::config::SandboxConfiguration;
23-
use hyperlight_host::sandbox_state::sandbox::{EvolvableSandbox, Sandbox};
24-
use hyperlight_host::sandbox_state::transition::{MultiUseContextCallback, Noop};
25-
use hyperlight_host::{GuestBinary, MultiUseSandbox, Result, UninitializedSandbox, new_error};
21+
use hyperlight_host::{GuestBinary, Result, UninitializedSandbox, new_error};
2622

2723
use super::metrics::{METRIC_ACTIVE_PROTO_WASM_SANDBOXES, METRIC_TOTAL_PROTO_WASM_SANDBOXES};
2824
use super::sandbox_builder::SandboxBuilder;
@@ -39,8 +35,6 @@ pub struct ProtoWasmSandbox {
3935
pub(super) inner: Option<UninitializedSandbox>,
4036
}
4137

42-
impl Sandbox for ProtoWasmSandbox {}
43-
4438
impl Registerable for ProtoWasmSandbox {
4539
fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
4640
&mut self,
@@ -95,27 +89,20 @@ impl ProtoWasmSandbox {
9589
/// The returned `WasmSandbox` can be then be cached and used to load a different Wasm module.
9690
///
9791
pub fn load_runtime(mut self) -> Result<WasmSandbox> {
98-
let multi_use_sandbox: MultiUseSandbox = match self.inner.take() {
99-
Some(s) => s.evolve(Noop::default())?,
92+
let mut sandbox = match self.inner.take() {
93+
Some(s) => s.evolve()?,
10094
None => return Err(new_error!("No inner sandbox found.")),
10195
};
10296

103-
let func = Box::new(move |call_ctx: &mut MultiUseGuestCallContext| {
104-
let res: i32 = call_ctx.call("InitWasmRuntime", ())?;
105-
if res != 0 {
106-
return Err(new_error!(
107-
"InitWasmRuntime Failed with error code {:?}",
108-
res
109-
));
110-
}
111-
Ok(())
112-
});
113-
114-
let transition_func = MultiUseContextCallback::from(func);
115-
116-
let new_sbox: MultiUseSandbox = multi_use_sandbox.evolve(transition_func)?;
97+
let res: i32 = sandbox.call_guest_function_by_name("InitWasmRuntime", ())?;
98+
if res != 0 {
99+
return Err(new_error!(
100+
"InitWasmRuntime Failed with error code {:?}",
101+
res
102+
));
103+
}
117104

118-
Ok(WasmSandbox::new(new_sbox))
105+
WasmSandbox::new(sandbox)
119106
}
120107

121108
/// Register the given host function `host_func` with `self` under

0 commit comments

Comments
 (0)