1use std::fmt::Debug;
17use std::sync::Arc;
18
19use hyperlight_host::hypervisor::InterruptHandle;
20use hyperlight_host::sandbox::snapshot::Snapshot;
21use hyperlight_host::HyperlightError::{self, JsonConversionFailure};
22use hyperlight_host::{MultiUseSandbox, Result};
23use tokio::task::JoinHandle;
24use tracing::{instrument, Level};
25
26#[cfg(feature = "guest-call-stats")]
27use super::execution_stats::ExecutionStats;
28use super::js_sandbox::JSSandbox;
29use super::metrics::{METRIC_SANDBOX_LOADS, METRIC_SANDBOX_UNLOADS};
30use super::monitor::runtime::get_monitor_runtime;
31use super::monitor::MonitorSet;
32#[cfg(feature = "function_call_metrics")]
33use crate::sandbox::metrics::EventHandlerMetricGuard;
34use crate::sandbox::metrics::SandboxMetricsGuard;
35
36pub struct LoadedJSSandbox {
38 inner: MultiUseSandbox,
39 snapshot: Arc<Snapshot>,
42 _metric_guard: SandboxMetricsGuard<LoadedJSSandbox>,
44 #[cfg(feature = "guest-call-stats")]
47 last_call_stats: Option<ExecutionStats>,
48}
49
50struct MonitorTask(JoinHandle<()>);
57
58impl Drop for MonitorTask {
59 fn drop(&mut self) {
60 self.0.abort();
61 }
62}
63
64impl LoadedJSSandbox {
65 #[instrument(err(Debug), skip_all, level=Level::INFO)]
66 pub(super) fn new(inner: MultiUseSandbox, snapshot: Arc<Snapshot>) -> Result<LoadedJSSandbox> {
67 metrics::counter!(METRIC_SANDBOX_LOADS).increment(1);
68 Ok(LoadedJSSandbox {
69 inner,
70 snapshot,
71 _metric_guard: SandboxMetricsGuard::new(),
72 #[cfg(feature = "guest-call-stats")]
73 last_call_stats: None,
74 })
75 }
76
77 #[instrument(err(Debug), skip(self, event, gc), level=Level::INFO)]
79 pub fn handle_event<F>(
80 &mut self,
81 func_name: F,
82 event: String,
83 gc: Option<bool>,
84 ) -> Result<String>
85 where
86 F: Into<String> + std::fmt::Debug,
87 {
88 let _json_val: serde_json::Value =
91 serde_json::from_str(&event).map_err(JsonConversionFailure)?;
92
93 let should_gc = gc.unwrap_or(true);
94 let func_name = func_name.into();
95 if func_name.is_empty() {
96 return Err(HyperlightError::Error(
97 "Handler name must not be empty".to_string(),
98 ));
99 }
100
101 #[cfg(feature = "function_call_metrics")]
102 let _metric_guard = EventHandlerMetricGuard::new(&func_name, should_gc);
103
104 #[cfg(feature = "guest-call-stats")]
106 let wall_start = std::time::Instant::now();
107
108 #[cfg(all(feature = "guest-call-stats", feature = "monitor-cpu-time"))]
109 let cpu_start = super::monitor::cpu_time::ThreadCpuHandle::for_current_thread()
110 .and_then(|h| h.elapsed().map(|t| (h, t)));
111
112 let result = self.inner.call(&func_name, (event, should_gc));
113
114 #[cfg(feature = "guest-call-stats")]
117 {
118 #[cfg(feature = "monitor-cpu-time")]
119 let cpu_time = cpu_start.and_then(|(handle, start_ticks)| {
120 handle.elapsed().map(|end_ticks| {
121 let delta_nanos =
122 handle.ticks_to_approx_nanos(end_ticks.saturating_sub(start_ticks));
123 std::time::Duration::from_nanos(delta_nanos)
124 })
125 });
126 #[cfg(not(feature = "monitor-cpu-time"))]
127 let cpu_time: Option<std::time::Duration> = None;
128
129 let wall_clock = wall_start.elapsed();
130
131 self.last_call_stats = Some(ExecutionStats {
132 wall_clock,
133 cpu_time,
134 terminated_by: None,
135 });
136 }
137
138 result
139 }
140
141 #[cfg(feature = "guest-call-stats")]
161 pub fn last_call_stats(&self) -> Option<&ExecutionStats> {
162 self.last_call_stats.as_ref()
163 }
164
165 #[instrument(err(Debug), skip_all, level=Level::DEBUG)]
167 pub fn unload(self) -> Result<JSSandbox> {
168 JSSandbox::from_loaded(self.inner, self.snapshot).inspect(|_| {
169 metrics::counter!(METRIC_SANDBOX_UNLOADS).increment(1);
170 })
171 }
172
173 #[instrument(err(Debug), skip_all, level=Level::DEBUG)]
176 pub fn snapshot(&mut self) -> Result<Arc<Snapshot>> {
177 self.inner.snapshot()
178 }
179
180 #[instrument(err(Debug), skip_all, level=Level::DEBUG)]
182 pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()> {
183 self.inner.restore(snapshot)?;
184 Ok(())
185 }
186
187 pub fn interrupt_handle(&self) -> Arc<dyn InterruptHandle> {
190 self.inner.interrupt_handle()
191 }
192
193 pub fn poisoned(&self) -> bool {
202 self.inner.poisoned()
203 }
204
205 #[instrument(err(Debug), skip(self, event, monitor, gc), level=Level::INFO)]
259 pub fn handle_event_with_monitor<F, M>(
260 &mut self,
261 func_name: F,
262 event: String,
263 monitor: &M,
264 gc: Option<bool>,
265 ) -> Result<String>
266 where
267 F: Into<String> + std::fmt::Debug,
268 M: MonitorSet,
269 {
270 let func_name = func_name.into();
271 if func_name.is_empty() {
272 return Err(HyperlightError::Error(
273 "Handler name must not be empty".to_string(),
274 ));
275 }
276 let interrupt_handle = self.interrupt_handle();
277
278 let racing_future = monitor.to_race().map_err(|e| {
283 tracing::error!("Failed to initialize execution monitor: {}", e);
284 HyperlightError::Error(format!("Execution monitor failed to start: {}", e))
285 })?;
286
287 let runtime = get_monitor_runtime().ok_or_else(|| {
295 tracing::error!("Monitor runtime is unavailable");
296 HyperlightError::Error("Monitor runtime is unavailable".to_string())
297 })?;
298
299 let terminated_by = Arc::new(std::sync::Mutex::new(None::<&'static str>));
303 let terminated_by_writer = terminated_by.clone();
304
305 let _monitor_task = MonitorTask(runtime.spawn(async move {
306 let winner = racing_future.await;
307 super::monitor::record_monitor_triggered(winner);
308 if let Ok(mut guard) = terminated_by_writer.lock() {
312 *guard = Some(winner);
313 }
314 interrupt_handle.kill();
315 }));
316
317 let result = self.handle_event(&func_name, event, gc);
320
321 #[cfg(feature = "guest-call-stats")]
325 if let Ok(guard) = terminated_by.lock()
326 && let Some(winner) = *guard
327 && let Some(stats) = &mut self.last_call_stats
328 {
329 stats.terminated_by = Some(winner);
330 }
331
332 result
333 }
334
335 #[cfg(feature = "crashdump")]
371 pub fn generate_crashdump(&self) -> Result<()> {
372 self.inner.generate_crashdump()
373 }
374}
375
376impl Debug for LoadedJSSandbox {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 f.debug_struct("LoadedJSSandbox").finish()
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385 use crate::{SandboxBuilder, Script};
386
387 fn get_valid_handler() -> Script {
388 Script::from_content(
389 r#"
390 function handler(event) {
391 event.request.uri = "/redirected.html";
392 return event
393 }
394 "#,
395 )
396 }
397
398 fn get_valid_event() -> String {
399 r#"
400 {
401 "request": {
402 "uri": "/index.html"
403 }
404 }
405 "#
406 .to_string()
407 }
408
409 fn get_static_counter_handler() -> Script {
410 Script::from_content(
411 r#"
412 let count = 0;
413 function handler(event) {
414 event.count = ++count;
415 return event
416 }
417 "#,
418 )
419 }
420
421 fn get_static_counter_event() -> String {
422 r#"
423 {
424 "count": 0
425 }
426 "#
427 .to_string()
428 }
429
430 fn get_loaded_sandbox() -> Result<LoadedJSSandbox> {
431 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
432 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
433
434 sandbox.add_handler("handler", get_valid_handler()).unwrap();
435
436 sandbox.get_loaded_sandbox()
437 }
438
439 #[test]
440 fn test_handle_event() {
441 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
442 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
443
444 sandbox.add_handler("handler", get_valid_handler()).unwrap();
445
446 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
447 let gc = Some(true);
448 let result = loaded_js_sandbox.handle_event("handler".to_string(), get_valid_event(), gc);
449
450 assert!(result.is_ok());
451 }
452
453 #[test]
454 fn test_handle_event_accumulates_state() {
455 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
456 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
457 sandbox
458 .add_handler("handler", get_static_counter_handler())
459 .unwrap();
460
461 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
462 let gc = Some(true);
463 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
464
465 assert!(result.is_ok());
466 let response = result.unwrap();
467 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
468 assert_eq!(response_json["count"], 1);
469
470 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
471 assert!(result.is_ok());
472 let response = result.unwrap();
473 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
474 assert_eq!(response_json["count"], 2);
475 }
476
477 #[test]
478 fn test_snapshot_and_restore() {
479 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
480 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
481
482 sandbox
483 .add_handler("handler", get_static_counter_handler())
484 .unwrap();
485
486 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
487 let gc = Some(true);
488
489 let result = loaded_js_sandbox
490 .handle_event("handler", get_static_counter_event(), gc)
491 .unwrap();
492
493 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
494 assert_eq!(response_json["count"], 1);
495
496 let snapshot = loaded_js_sandbox.snapshot().unwrap();
498
499 let result = loaded_js_sandbox
501 .handle_event("handler", get_static_counter_event(), gc)
502 .unwrap();
503 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
504 assert_eq!(response_json["count"], 2);
505
506 let result = loaded_js_sandbox
507 .handle_event("handler", get_static_counter_event(), gc)
508 .unwrap();
509 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
510 assert_eq!(response_json["count"], 3);
511
512 loaded_js_sandbox.restore(snapshot.clone()).unwrap();
514
515 let result = loaded_js_sandbox
517 .handle_event("handler", get_static_counter_event(), gc)
518 .unwrap();
519 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
520 assert_eq!(response_json["count"], 2);
521
522 let mut js_sandbox = loaded_js_sandbox.unload().unwrap();
524
525 js_sandbox
526 .add_handler("handler2", get_static_counter_handler())
527 .unwrap();
528
529 let mut reloaded_js_sandbox = js_sandbox.get_loaded_sandbox().unwrap();
530
531 let result = reloaded_js_sandbox
533 .handle_event("handler2", get_static_counter_event(), gc)
534 .unwrap();
535 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
536 assert_eq!(response_json["count"], 1);
537
538 reloaded_js_sandbox
539 .handle_event("handler", get_static_counter_event(), gc)
540 .unwrap_err();
541
542 reloaded_js_sandbox.restore(snapshot.clone()).unwrap();
544 let result = reloaded_js_sandbox
546 .handle_event("handler", get_static_counter_event(), gc)
547 .unwrap();
548 let response_json: serde_json::Value = serde_json::from_str(&result).unwrap();
549 assert_eq!(response_json["count"], 2);
550
551 reloaded_js_sandbox
553 .handle_event("handler2", get_static_counter_event(), gc)
554 .unwrap_err();
555 }
556
557 #[test]
558 fn test_add_handler_unload_and_reuse_resets_state() {
559 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
560 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
561 sandbox
562 .add_handler("handler", get_static_counter_handler())
563 .unwrap();
564 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
565 let gc = Some(true);
566
567 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
568 assert!(result.is_ok());
569 let response = result.unwrap();
570 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
571 assert_eq!(response_json["count"], 1);
572
573 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
574 assert!(result.is_ok());
575 let response = result.unwrap();
576 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
577 assert_eq!(response_json["count"], 2);
578
579 let mut sandbox = loaded_js_sandbox.unload().unwrap();
581 sandbox
582 .add_handler("handler", get_static_counter_handler())
583 .unwrap();
584 let mut loaded_js_sandbox = sandbox.get_loaded_sandbox().unwrap();
586 let gc = Some(true);
587
588 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
589 assert!(result.is_ok());
590 let response = result.unwrap();
591 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
592 assert_eq!(response_json["count"], 1);
593
594 let result = loaded_js_sandbox.handle_event("handler", get_static_counter_event(), gc);
595 assert!(result.is_ok());
596 let response = result.unwrap();
597 let response_json: serde_json::Value = serde_json::from_str(&response).unwrap();
598 assert_eq!(response_json["count"], 2);
599 }
600
601 #[test]
602 fn test_unload() {
603 let sandbox = get_loaded_sandbox().unwrap();
604
605 let result = sandbox.unload();
606
607 assert!(result.is_ok());
608 }
609
610 use crate::sandbox::monitor::ExecutionMonitor;
611
612 struct FailingMonitor;
615
616 impl ExecutionMonitor for FailingMonitor {
617 fn get_monitor(
618 &self,
619 ) -> hyperlight_host::Result<impl std::future::Future<Output = ()> + Send + 'static>
620 {
621 Err::<std::future::Ready<()>, _>(hyperlight_host::HyperlightError::Error(
622 "Simulated initialization failure".to_string(),
623 ))
624 }
625
626 fn name(&self) -> &'static str {
627 "failing-monitor"
628 }
629 }
630
631 #[test]
632 fn test_handle_event_with_monitor_fails_if_monitor_cannot_start() {
633 let mut loaded = get_loaded_sandbox().unwrap();
634 let monitor = FailingMonitor;
635
636 let result = loaded.handle_event_with_monitor("handler", get_valid_event(), &monitor, None);
638
639 assert!(result.is_err(), "Should fail when monitor can't start");
640 let err = result.unwrap_err();
641 assert!(
642 err.to_string().contains("failed to start"),
643 "Error should mention monitor failure: {}",
644 err
645 );
646
647 assert!(
649 !loaded.poisoned(),
650 "Sandbox should not be poisoned when monitor fails to start"
651 );
652 }
653}