1use std::fmt::Debug;
18use std::sync::Arc;
19
20use hyperlight_host::func::{ParameterTuple, SupportedReturnType};
21use hyperlight_host::hypervisor::InterruptHandle;
22use hyperlight_host::sandbox::snapshot::Snapshot;
23use hyperlight_host::sandbox::{Callable, SandboxStatus};
24use hyperlight_host::{MultiUseSandbox, Result, log_then_return, new_error};
25
26use super::metrics::METRIC_TOTAL_LOADED_WASM_SANDBOXES;
27use super::wasm_sandbox::WasmSandbox;
28use crate::sandbox::metrics::{METRIC_ACTIVE_LOADED_WASM_SANDBOXES, METRIC_SANDBOX_UNLOADS};
29
30pub struct LoadedWasmSandbox {
40 inner: Option<MultiUseSandbox>,
44 runtime_snapshot: Option<Arc<Snapshot>>,
46}
47
48impl LoadedWasmSandbox {
49 pub fn call_guest_function<Output: SupportedReturnType>(
69 &mut self,
70 fn_name: &str,
71 params: impl ParameterTuple,
72 ) -> Result<Output> {
73 match &mut self.inner {
74 Some(inner) => inner.call(fn_name, params),
75 None => log_then_return!("No inner MultiUseSandbox to call"),
76 }
77 }
78
79 pub fn snapshot(&mut self) -> Result<Arc<Snapshot>> {
90 match &mut self.inner {
91 Some(inner) => inner.snapshot(),
92 None => log_then_return!("No inner MultiUseSandbox to snapshot"),
93 }
94 }
95
96 pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()> {
109 match &mut self.inner {
110 Some(inner) => inner.restore(snapshot),
111 None => log_then_return!("No inner MultiUseSandbox to restore"),
112 }
113 }
114
115 pub fn unload_module(mut self) -> Result<WasmSandbox> {
124 let sandbox = self
125 .inner
126 .take()
127 .ok_or_else(|| new_error!("No inner MultiUseSandbox to unload"))?;
128
129 let snapshot = self
130 .runtime_snapshot
131 .take()
132 .ok_or_else(|| new_error!("No snapshot of the WasmSandbox to unload"))?;
133
134 WasmSandbox::new_from_loaded(sandbox, snapshot).inspect(|_| {
135 metrics::counter!(METRIC_SANDBOX_UNLOADS).increment(1);
136 })
137 }
138
139 pub(super) fn new(
140 inner: MultiUseSandbox,
141 runtime_snapshot: Arc<Snapshot>,
142 ) -> Result<LoadedWasmSandbox> {
143 metrics::gauge!(METRIC_ACTIVE_LOADED_WASM_SANDBOXES).increment(1);
144 metrics::counter!(METRIC_TOTAL_LOADED_WASM_SANDBOXES).increment(1);
145 Ok(LoadedWasmSandbox {
146 inner: Some(inner),
147 runtime_snapshot: Some(runtime_snapshot),
148 })
149 }
150
151 pub fn interrupt_handle(&self) -> Result<Arc<dyn InterruptHandle>> {
154 if let Some(inner) = &self.inner {
155 Ok(inner.interrupt_handle())
156 } else {
157 Err(new_error!(
158 "WasmSandbox is None, cannot get interrupt handle"
159 ))
160 }
161 }
162
163 pub fn status(&self) -> Result<SandboxStatus> {
169 match &self.inner {
170 Some(inner) => Ok(inner.status()),
171 None => log_then_return!("No inner MultiUseSandbox to check status"),
172 }
173 }
174
175 #[deprecated(since = "0.15.0", note = "use status().is_poisoned() instead")]
199 pub fn is_poisoned(&self) -> Result<bool> {
200 Ok(self.status()?.is_poisoned())
201 }
202}
203
204impl Callable for LoadedWasmSandbox {
205 fn call<Output: SupportedReturnType>(
206 &mut self,
207 func_name: &str,
208 args: impl ParameterTuple,
209 ) -> Result<Output> {
210 self.call_guest_function(func_name, args)
211 }
212}
213
214impl Drop for LoadedWasmSandbox {
215 fn drop(&mut self) {
216 metrics::gauge!(METRIC_ACTIVE_LOADED_WASM_SANDBOXES).decrement(1);
217 }
218}
219
220impl Debug for LoadedWasmSandbox {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 f.debug_struct("LoadedWasmSandbox")
223 .field("inner", &self.inner)
224 .finish()
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use std::sync::Arc;
231 use std::thread;
232
233 use crossbeam_queue::ArrayQueue;
234 use examples_common::get_wasm_module_path;
235 use hyperlight_host::{HyperlightError, new_error};
236
237 use super::{LoadedWasmSandbox, WasmSandbox};
238 use crate::Result;
239 use crate::sandbox::proto_wasm_sandbox::ProtoWasmSandbox;
240 use crate::sandbox::sandbox_builder::SandboxBuilder;
241
242 fn get_time_since_boot_microsecond() -> Result<i64> {
243 let res = std::time::SystemTime::now()
244 .duration_since(std::time::SystemTime::UNIX_EPOCH)?
245 .as_micros();
246 i64::try_from(res).map_err(HyperlightError::IntConversionFailure)
247 }
248
249 #[test]
252 fn test_call_guest_functions_with_default_config_multiple_times() {
253 let mut sandbox = ProtoWasmSandbox::default();
254
255 sandbox
256 .register(
257 "GetTimeSinceBootMicrosecond",
258 get_time_since_boot_microsecond,
259 )
260 .unwrap();
261
262 let wasm_sandbox = sandbox.load_runtime().unwrap();
263 let loaded_wasm_sandbox: LoadedWasmSandbox = {
264 let mod_path = get_wasm_module_path("RunWasm.aot").unwrap();
265 wasm_sandbox.load_module(mod_path)
266 }
267 .unwrap();
268
269 call_funcs(loaded_wasm_sandbox, 500);
270 }
271
272 #[test]
273 fn test_sandbox_use_on_different_threads() {
274 let wasm_sandbox_queue = Arc::new(ArrayQueue::<WasmSandbox>::new(10));
275 let loaded_wasm_sandbox_queue = Arc::new(ArrayQueue::<LoadedWasmSandbox>::new(10));
276
277 for i in 0..10 {
279 println!("Creating WasmSandbox instance {}", i);
280 let mut sandbox = ProtoWasmSandbox::default();
281
282 sandbox
283 .register(
284 "GetTimeSinceBootMicrosecond",
285 get_time_since_boot_microsecond,
286 )
287 .unwrap();
288
289 let wasm_sandbox = sandbox.load_runtime().unwrap();
290 wasm_sandbox_queue.push(wasm_sandbox).unwrap();
291 println!("Pushed WasmSandbox instance {}", i);
292 }
293
294 let thread_handles: Vec<_> = (0..10)
297 .map(|i| {
298 let wq = wasm_sandbox_queue.clone();
299 let lwq = loaded_wasm_sandbox_queue.clone();
300
301 thread::spawn(move || {
302 println!("Loading module on thread {}", i);
303 let wasm_sandbox = wq.pop().unwrap();
304 let loaded_wasm_sandbox: LoadedWasmSandbox = {
305 let mod_path = get_wasm_module_path("RunWasm.aot").unwrap();
306 wasm_sandbox.load_module(mod_path)
307 }
308 .unwrap();
309 println!("Calling function on thread {}", i);
310 let lws = call_funcs(loaded_wasm_sandbox, 1);
311 lwq.push(lws).unwrap();
312 println!("Pushed LoadedWasmSandbox instance to queue on thread {}", i)
313 })
314 })
315 .collect::<Vec<_>>();
316
317 for handle in thread_handles {
318 handle.join().unwrap();
319 }
320
321 let thread_handles: Vec<_> = (0..10)
325 .map(|i| {
326 let wq = wasm_sandbox_queue.clone();
327 let lwq = loaded_wasm_sandbox_queue.clone();
328
329 thread::spawn(move || {
330 println!("Popping sandbox on thread {}", i);
331 let loaded_wasm_sandbox = lwq.pop().unwrap();
332 println!("Calling funcs on thread {}", i);
333 let lws = call_funcs(loaded_wasm_sandbox, 1);
334 println!("Unloading module on thread {}", i);
335 let ws = lws.unload_module().unwrap();
336 println!("Pusing WasmSandbox on thread {}", i);
337 wq.push(ws).unwrap();
338 })
339 })
340 .collect::<Vec<_>>();
341
342 for handle in thread_handles {
343 handle.join().unwrap();
344 }
345
346 let thread_handles: Vec<_> = (0..10)
350 .map(|i| {
351 let wq = wasm_sandbox_queue.clone();
352
353 thread::spawn(move || {
354 println!("Popping WasmSandbox on thread {}", i);
355 let wasm_sandbox = wq.pop().unwrap();
356 println!("Loading module on thread {}", i);
357 let loaded_wasm_sandbox: LoadedWasmSandbox = {
358 let mod_path = get_wasm_module_path("RunWasm.aot").unwrap();
359 wasm_sandbox.load_module(mod_path)
360 }
361 .unwrap();
362 println!("Calling function on thread {}", i);
363 call_funcs(loaded_wasm_sandbox, 1);
364 })
365 })
366 .collect::<Vec<_>>();
367
368 for handle in thread_handles {
369 handle.join().unwrap();
370 }
371 }
372
373 #[test]
374 fn test_call_guest_functions_with_custom_config_multiple_times() {
375 let mut sandbox = SandboxBuilder::new()
376 .with_guest_scratch_size(32 * 1024)
377 .with_guest_heap_size(128 * 1024)
378 .build()
379 .unwrap();
380
381 sandbox
382 .register(
383 "GetTimeSinceBootMicrosecond",
384 get_time_since_boot_microsecond,
385 )
386 .unwrap();
387
388 let wasm_sandbox = sandbox.load_runtime().unwrap();
389
390 let loaded_wasm_sandbox: LoadedWasmSandbox = {
391 let mod_path = get_wasm_module_path("RunWasm.aot").unwrap();
392 wasm_sandbox.load_module(mod_path)
393 }
394 .unwrap();
395
396 call_funcs(loaded_wasm_sandbox, 1000);
397 }
398
399 #[test]
400 fn test_call_host_func_with_vecbytes() {
401 let host_func = |b: Vec<u8>, l: i32| {
402 let s = std::str::from_utf8(&b).unwrap();
405 println!("Host function received buffer: {}", s);
406
407 if s != "Hello World!" {
409 return Err(new_error!("Unexpected value in buffer {}", s));
410 }
411
412 if l != 12 {
413 return Err(new_error!("Unexpected length of buffer {}", l));
414 }
415 Ok(0i32)
416 };
417
418 let mut proto_wasm_sandbox = SandboxBuilder::new().build().unwrap();
419
420 proto_wasm_sandbox
421 .register("HostFuncWithBufferAndLength", host_func)
422 .unwrap();
423
424 let wasm_sandbox = proto_wasm_sandbox.load_runtime().unwrap();
425
426 let mut loaded_wasm_sandbox: LoadedWasmSandbox = {
427 let mod_path = get_wasm_module_path("HostFunction.aot").unwrap();
428 wasm_sandbox.load_module(mod_path)
429 }
430 .unwrap();
431
432 let r: i32 = loaded_wasm_sandbox
435 .call_guest_function("PassBufferAndLengthToHost", ())
436 .unwrap();
437
438 assert_eq!(r, 0);
439 }
440
441 #[test]
442 fn test_load_module_fails_with_missing_host_function() {
443 let proto_wasm_sandbox = SandboxBuilder::new().build().unwrap();
448
449 let wasm_sandbox = proto_wasm_sandbox.load_runtime().unwrap();
450
451 let result: std::result::Result<LoadedWasmSandbox, _> = {
452 let mod_path = get_wasm_module_path("HostFunction.aot").unwrap();
453 wasm_sandbox.load_module(mod_path)
454 };
455
456 let err = result.unwrap_err();
457 let err_msg = format!("{:?}", err);
458 assert!(
459 err_msg.contains("HostFuncWithBufferAndLength"),
460 "Error should mention the missing host function, got: {err_msg}"
461 );
462 }
463
464 fn call_funcs(
465 mut loaded_wasm_sandbox: LoadedWasmSandbox,
466 iterations: i32,
467 ) -> LoadedWasmSandbox {
468 for i in 0..iterations {
471 let result: i32 = loaded_wasm_sandbox
472 .call_guest_function("CalcFib", 4i32)
473 .unwrap();
474
475 println!(
476 "Got result: {:?} from the host function! iteration {}",
477 result, i,
478 );
479 }
480
481 for i in 0..iterations {
484 let result: String = loaded_wasm_sandbox
485 .call_guest_function(
486 "Echo",
487 "Message from Rust Example to Wasm Function".to_string(),
488 )
489 .unwrap();
490
491 println!(
492 "Got result: {:?} from the host function! iteration {}",
493 result, i,
494 );
495 }
496
497 for i in 0..iterations {
498 let result: String = loaded_wasm_sandbox
499 .call_guest_function(
500 "ToUpper",
501 "Message from Rust Example to WASM Function".to_string(),
502 )
503 .unwrap();
504
505 println!(
506 "Got result: {:?} from the host function! iteration {}",
507 result, i,
508 );
509
510 assert_eq!(
511 result,
512 "MESSAGE FROM RUST EXAMPLE TO WASM FUNCTION".to_string()
513 );
514 }
515
516 for i in 0..iterations {
519 let result: Vec<u8> = loaded_wasm_sandbox
520 .call_guest_function("ReceiveByteArray", (vec![0x01, 0x02, 0x03], 3i32))
521 .unwrap();
522
523 println!(
524 "Got result: {:?} from the host function! iteration {}",
525 result, i,
526 );
527 }
528
529 for i in 0..iterations {
532 loaded_wasm_sandbox
533 .call_guest_function::<()>(
534 "Print",
535 "Message from Rust Example to Wasm Function\n".to_string(),
536 )
537 .unwrap();
538
539 println!("Called the host function! iteration {}", i,);
540 }
541
542 for i in 0..iterations {
545 loaded_wasm_sandbox
546 .call_guest_function::<()>("PrintHelloWorld", ())
547 .unwrap();
548
549 println!("Called the host function! iteration {}", i,);
550 }
551
552 loaded_wasm_sandbox
553 }
554}