Expand description
Execution monitoring and enforcement (timeouts, resource limits, etc.). Execution monitoring for JavaScript sandbox handlers.
This module provides the [ExecutionMonitor] trait and built-in implementations
for monitoring and terminating handler execution based on resource limits.
§Architecture — Why two traits?
The monitoring system has a subtle design tension:
- Users want a simple trait to implement:
get_monitor()+name(). - The orchestrator needs to race multiple monitors, identifying which one fired (by name) for metrics and logging.
- Tuples of monitors (e.g.
(WallClockMonitor, CpuTimeMonitor)) are a composition of monitors, not a single monitor — they shouldn’t pretend to be one by implementingExecutionMonitor.
The solution: separate concerns into two traits.
- [
ExecutionMonitor] — User-facing. Only two methods:get_monitor()andname(). Simple, clean, no composition logic. - [
MonitorSet] — Internal (sealed). One method:to_race(). Produces a single racing future that completes when the first monitor fires, emitting metrics and logging the winner. Automatically derived for everyExecutionMonitorvia a blanket impl, and for tuples of up to 5 monitors viatokio::select!in a macro.
The orchestrator’s handle_event_with_monitor is bounded by
M: MonitorSet, not M: ExecutionMonitor. Users never need to know
MonitorSet exists — it’s sealed so they can’t implement it directly,
and it’s derived automatically via the blanket impl.
§Built-in Monitors
- [
WallClockMonitor] - Terminates execution after a wall-clock timeout (requiresmonitor-wall-clockfeature) - [
CpuTimeMonitor] - Terminates execution after a CPU time limit (requiresmonitor-cpu-timefeature)
§Usage
use hyperlight_js::{WallClockMonitor, CpuTimeMonitor, ExecutionMonitor};
use std::time::Duration;
// Single monitor — ExecutionMonitor auto-satisfies MonitorSet via blanket impl
let monitor = WallClockMonitor::new(Duration::from_secs(5))?;
let result = loaded_sandbox.handle_event_with_monitor(
"handler",
"{}".to_string(),
&monitor,
None,
)?;
// Multiple monitors — tuples implement MonitorSet with OR semantics.
// The first monitor to trigger terminates execution, and the winning
// monitor's name is logged so you know exactly which limit was breached.
let wall = WallClockMonitor::new(Duration::from_secs(5))?;
let cpu = CpuTimeMonitor::new(Duration::from_millis(500))?;
let result = loaded_sandbox.handle_event_with_monitor(
"handler",
"{}".to_string(),
&(wall, cpu),
None,
)?;§Custom Monitors
Implement [ExecutionMonitor] to create custom monitoring logic:
use hyperlight_js::ExecutionMonitor;
use hyperlight_host::Result;
use std::future::Future;
struct MyMonitor { limit: std::time::Duration }
impl ExecutionMonitor for MyMonitor {
fn get_monitor(&self) -> Result<impl Future<Output = ()> + Send + 'static> {
let limit = self.limit;
Ok(async move {
hyperlight_js::monitor::sleep(limit).await;
tracing::warn!("Custom limit exceeded");
})
}
fn name(&self) -> &'static str { "my-monitor" }
}§Fail-Closed Semantics
If any monitor fails to initialize (get_monitor() returns Err), the handler
is never executed. Execution cannot proceed unmonitored due to a monitor
initialization failure. This is a deliberate security-first design choice.
§Using Wall-Clock and CPU Monitors Together
Wall-clock and CPU monitors are designed to be used together as a tuple
(WallClockMonitor, CpuTimeMonitor) to provide comprehensive protection:
CpuTimeMonitorcatches compute-bound abuse (crypto mining, tight loops)WallClockMonitorcatches resource exhaustion where the guest consumes host resources without consuming CPU — e.g. blocking on host calls. A guest doing “nothing” in terms of CPU can still starve the host of resources (sometimes called a resource exhaustion attack or slowloris-style denial of service) Right now this is not really possible to do in Hyperlight since there is no way for the guest to block without consuming CPU, but we want to be prepared for when this is possible.
Neither alone is sufficient: CPU-only misses idle resource holding; wall-clock-only is unfair to legitimately I/O-heavy workloads.
§Runtime Configuration
The shared async runtime thread count can be configured via environment variable:
export HYPERLIGHT_MONITOR_THREADS=4 # Default is 2See the runtime module for details on the shared runtime.
Traits§
- Execution
Monitor - A monitor that enforces execution limits on handler invocations.
- Monitor
Set - A composable set of monitors that produces a single racing future.
Functions§
- sleep
- Async sleep function used by monitors.