1use std::fmt::Debug;
2use std::sync::Arc;
3
4use hyperlight_host::hypervisor::InterruptHandle;
5use hyperlight_host::sandbox::snapshot::Snapshot;
6use hyperlight_host::HyperlightError::{self, JsonConversionFailure};
7use hyperlight_host::{MultiUseSandbox, Result};
8use tokio::task::JoinHandle;
9use tracing::{instrument, Level};
10
11use super::js_sandbox::JSSandbox;
12use super::metrics::{METRIC_SANDBOX_LOADS, METRIC_SANDBOX_UNLOADS};
13use super::monitor::runtime::get_monitor_runtime;
14use super::monitor::MonitorSet;
15#[cfg(feature = "function_call_metrics")]
16use crate::sandbox::metrics::EventHandlerMetricGuard;
17use crate::sandbox::metrics::SandboxMetricsGuard;
18
19pub struct LoadedJSSandbox {
21 inner: MultiUseSandbox,
22 snapshot: Snapshot,
25 _metric_guard: SandboxMetricsGuard<LoadedJSSandbox>,
27}
28
29struct MonitorTask(JoinHandle<()>);
36
37impl Drop for MonitorTask {
38 fn drop(&mut self) {
39 self.0.abort();
40 }
41}
42
43impl LoadedJSSandbox {
44 #[instrument(err(Debug), skip_all, level=Level::INFO)]
45 pub(super) fn new(inner: MultiUseSandbox, snapshot: Snapshot) -> Result<LoadedJSSandbox> {
46 metrics::counter!(METRIC_SANDBOX_LOADS).increment(1);
47 Ok(LoadedJSSandbox {
48 inner,
49 snapshot,
50 _metric_guard: SandboxMetricsGuard::new(),
51 })
52 }
53
54 #[instrument(err(Debug), skip(self, event, gc), level=Level::INFO)]
56 pub fn handle_event<F>(
57 &mut self,
58 func_name: F,
59 event: String,
60 gc: Option<bool>,
61 ) -> Result<String>
62 where
63 F: Into<String> + std::fmt::Debug,
64 {
65 let _json_val: serde_json::Value =
68 serde_json::from_str(&event).map_err(JsonConversionFailure)?;
69
70 let should_gc = gc.unwrap_or(true);
71 let func_name = func_name.into();
72 if func_name.is_empty() {
73 return Err(HyperlightError::Error(
74 "Handler name must not be empty".to_string(),
75 ));
76 }
77
78 #[cfg(feature = "function_call_metrics")]
79 let _metric_guard = EventHandlerMetricGuard::new(&func_name, should_gc);
80
81 self.inner.call(&func_name, (event, should_gc))
82 }
83
84 #[instrument(err(Debug), skip_all, level=Level::DEBUG)]
86 pub fn unload(self) -> Result<JSSandbox> {
87 JSSandbox::from_loaded(self.inner, self.snapshot).inspect(|_| {
88 metrics::counter!(METRIC_SANDBOX_UNLOADS).increment(1);
89 })
90 }
91
92 #[instrument(err(Debug), skip_all, level=Level::DEBUG)]
95 pub fn snapshot(&mut self) -> Result<Snapshot> {
96 self.inner.snapshot()
97 }
98
99 #[instrument(err(Debug), skip_all, level=Level::DEBUG)]
101 pub fn restore(&mut self, snapshot: &Snapshot) -> Result<()> {
102 self.inner.restore(snapshot)?;
103 Ok(())
104 }
105
106 pub fn interrupt_handle(&self) -> Arc<dyn InterruptHandle> {
109 self.inner.interrupt_handle()
110 }
111
112 pub fn poisoned(&self) -> bool {
121 self.inner.poisoned()
122 }
123
124 #[instrument(err(Debug), skip(self, event, monitor, gc), level=Level::INFO)]
178 pub fn handle_event_with_monitor<F, M>(
179 &mut self,
180 func_name: F,
181 event: String,
182 monitor: &M,
183 gc: Option<bool>,
184 ) -> Result<String>
185 where
186 F: Into<String> + std::fmt::Debug,
187 M: MonitorSet,
188 {
189 let func_name = func_name.into();
190 if func_name.is_empty() {
191 return Err(HyperlightError::Error(
192 "Handler name must not be empty".to_string(),
193 ));
194 }
195 let interrupt_handle = self.interrupt_handle();
196
197 let racing_future = monitor.to_race().map_err(|e| {
202 tracing::error!("Failed to initialize execution monitor: {}", e);
203 HyperlightError::Error(format!("Execution monitor failed to start: {}", e))
204 })?;
205
206 let runtime = get_monitor_runtime().ok_or_else(|| {
213 tracing::error!("Monitor runtime is unavailable");
214 HyperlightError::Error("Monitor runtime is unavailable".to_string())
215 })?;
216
217 let _monitor_task = MonitorTask(runtime.spawn(async move {
218 racing_future.await;
219 interrupt_handle.kill();
220 }));
221
222 self.handle_event(&func_name, event, gc)
225 }
226
227 #[cfg(feature = "crashdump")]
263 pub fn generate_crashdump(&self) -> Result<()> {
264 self.inner.generate_crashdump()
265 }
266}
267
268impl Debug for LoadedJSSandbox {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.debug_struct("LoadedJSSandbox").finish()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::{SandboxBuilder, Script};
278
279 fn get_valid_handler() -> Script {
280 Script::from_content(
281 r#"
282 function handler(event) {
283 event.request.uri = "/redirected.html";
284 return event
285 }
286 "#,
287 )
288 }
289
290 fn get_valid_event() -> String {
291 r#"
292 {
293 "request": {
294 "uri": "/index.html"
295 }
296 }
297 "#
298 .to_string()
299 }
300
301 fn get_static_counter_handler() -> Script {
302 Script::from_content(
303 r#"
304 let count = 0;
305 function handler(event) {
306 event.count = ++count;
307 return event
308 }
309 "#,
310 )
311 }
312
313 fn get_static_counter_event() -> String {
314 r#"
315 {
316 "count": 0
317 }
318 "#
319 .to_string()
320 }
321
322 fn get_loaded_sandbox() -> Result<LoadedJSSandbox> {
323 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
324 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
325
326 sandbox.add_handler("handler", get_valid_handler()).unwrap();
327
328 sandbox.get_loaded_sandbox()
329 }
330
331 #[test]
332 fn test_handle_event() {
333 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
334 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
335
336 sandbox.add_handler("handler", get_valid_handler()).unwrap();
337
338 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
339 let gc = Some(true);
340 let result = loaded_js_sandbox.handle_event("handler".to_string(), get_valid_event(), gc);
341
342 assert!(result.is_ok());
343 }
344
345 #[test]
346 fn test_handle_event_accumulates_state() {
347 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
348 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
349 sandbox
350 .add_handler("handler", get_static_counter_handler())
351 .unwrap();
352
353 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
354 let gc = Some(true);
355 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
356
357 assert!(result.is_ok());
358 let response = result.unwrap();
359 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
360 assert_eq!(response_json["count"], 1);
361
362 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
363 assert!(result.is_ok());
364 let response = result.unwrap();
365 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
366 assert_eq!(response_json["count"], 2);
367 }
368
369 #[test]
370 fn test_snapshot_and_restore() {
371 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
372 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
373
374 sandbox
375 .add_handler("handler", get_static_counter_handler())
376 .unwrap();
377
378 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
379 let gc = Some(true);
380
381 let result = loaded_js_sandbox
382 .handle_event("handler", get_static_counter_event(), gc)
383 .unwrap();
384
385 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
386 assert_eq!(response_json["count"], 1);
387
388 let snapshot = loaded_js_sandbox.snapshot().unwrap();
390
391 let result = loaded_js_sandbox
393 .handle_event("handler", get_static_counter_event(), gc)
394 .unwrap();
395 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
396 assert_eq!(response_json["count"], 2);
397
398 let result = loaded_js_sandbox
399 .handle_event("handler", get_static_counter_event(), gc)
400 .unwrap();
401 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
402 assert_eq!(response_json["count"], 3);
403
404 loaded_js_sandbox.restore(&snapshot).unwrap();
406
407 let result = loaded_js_sandbox
409 .handle_event("handler", get_static_counter_event(), gc)
410 .unwrap();
411 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
412 assert_eq!(response_json["count"], 2);
413
414 let mut js_sandbox = loaded_js_sandbox.unload().unwrap();
416
417 js_sandbox
418 .add_handler("handler2", get_static_counter_handler())
419 .unwrap();
420
421 let mut reloaded_js_sandbox = js_sandbox.get_loaded_sandbox().unwrap();
422
423 let result = reloaded_js_sandbox
425 .handle_event("handler2", get_static_counter_event(), gc)
426 .unwrap();
427 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
428 assert_eq!(response_json["count"], 1);
429
430 reloaded_js_sandbox
431 .handle_event("handler", get_static_counter_event(), gc)
432 .unwrap_err();
433
434 reloaded_js_sandbox.restore(&snapshot).unwrap();
436 let result = reloaded_js_sandbox
438 .handle_event("handler", get_static_counter_event(), gc)
439 .unwrap();
440 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
441 assert_eq!(response_json["count"], 2);
442
443 reloaded_js_sandbox
445 .handle_event("handler2", get_static_counter_event(), gc)
446 .unwrap_err();
447 }
448
449 #[test]
450 fn test_add_handler_unload_and_reuse_resets_state() {
451 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
452 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
453 sandbox
454 .add_handler("handler", get_static_counter_handler())
455 .unwrap();
456 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
457 let gc = Some(true);
458
459 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
460 assert!(result.is_ok());
461 let response = result.unwrap();
462 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
463 assert_eq!(response_json["count"], 1);
464
465 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
466 assert!(result.is_ok());
467 let response = result.unwrap();
468 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
469 assert_eq!(response_json["count"], 2);
470
471 let mut sandbox = loaded_js_sandbox.unload().unwrap();
473 sandbox
474 .add_handler("handler", get_static_counter_handler())
475 .unwrap();
476 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
478 let gc = Some(true);
479
480 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
481 assert!(result.is_ok());
482 let response = result.unwrap();
483 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
484 assert_eq!(response_json["count"], 1);
485
486 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
487 assert!(result.is_ok());
488 let response = result.unwrap();
489 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
490 assert_eq!(response_json["count"], 2);
491 }
492
493 #[test]
494 fn test_unload() {
495 let sandbox = get_loaded_sandbox().unwrap();
496
497 let result = sandbox.unload();
498
499 assert!(result.is_ok());
500 }
501
502 use crate::sandbox::monitor::ExecutionMonitor;
503
504 struct FailingMonitor;
507
508 impl ExecutionMonitor for FailingMonitor {
509 fn get_monitor(
510 &self,
511 ) -> hyperlight_host::Result<impl std::future::Future<Output = ()> + Send + 'static>
512 {
513 Err::<std::future::Ready<()>, _>(hyperlight_host::HyperlightError::Error(
514 "Simulated initialization failure".to_string(),
515 ))
516 }
517
518 fn name(&self) -> &'static str {
519 "failing-monitor"
520 }
521 }
522
523 #[test]
524 fn test_handle_event_with_monitor_fails_if_monitor_cannot_start() {
525 let mut loaded = get_loaded_sandbox().unwrap();
526 let monitor = FailingMonitor;
527
528 let result = loaded.handle_event_with_monitor("handler", get_valid_event(), &monitor, None);
530
531 assert!(result.is_err(), "Should fail when monitor can't start");
532 let err = result.unwrap_err();
533 assert!(
534 err.to_string().contains("failed to start"),
535 "Error should mention monitor failure: {}",
536 err
537 );
538
539 assert!(
541 !loaded.poisoned(),
542 "Sandbox should not be poisoned when monitor fails to start"
543 );
544 }
545}