Skip to main content

Module monitor

Module monitor 

Source
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:

  1. Users want a simple trait to implement: get_monitor() + name().
  2. The orchestrator needs to race multiple monitors, identifying which one fired (by name) for metrics and logging.
  3. Tuples of monitors (e.g. (WallClockMonitor, CpuTimeMonitor)) are a composition of monitors, not a single monitor — they shouldn’t pretend to be one by implementing ExecutionMonitor.

The solution: separate concerns into two traits.

  • [ExecutionMonitor] — User-facing. Only two methods: get_monitor() and name(). 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 every ExecutionMonitor via a blanket impl, and for tuples of up to 5 monitors via tokio::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 (requires monitor-wall-clock feature)
  • [CpuTimeMonitor] - Terminates execution after a CPU time limit (requires monitor-cpu-time feature)

§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:

  • CpuTimeMonitor catches compute-bound abuse (crypto mining, tight loops)
  • WallClockMonitor catches 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 2

See the runtime module for details on the shared runtime.

Traits§

ExecutionMonitor
A monitor that enforces execution limits on handler invocations.
MonitorSet
A composable set of monitors that produces a single racing future.

Functions§

sleep
Async sleep function used by monitors.