Skip to content

Commit eeca10c

Browse files
committed
Updates for the latest hypleright version
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent 5f7e9ae commit eeca10c

15 files changed

Lines changed: 145 additions & 136 deletions

File tree

Cargo.lock

Lines changed: 63 additions & 77 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ repository = "https://github.com/hyperlight-dev/hyperlight-js"
1111
readme = "README.md"
1212

1313
[workspace.dependencies]
14-
hyperlight-common = { version = "0.12", default-features = false }
15-
hyperlight-guest-bin = { version = "0.12" }
16-
hyperlight-guest = { version = "0.12" }
17-
hyperlight-host = { version = "0.12", default-features = false, features = ["executable_heap", "init-paging"] }
14+
hyperlight-common = { git = "https://github.com/hyperlight-dev/hyperlight", rev = "620339aa95d508e8cbd1d38b4374f09090aade7b", default-features = false }
15+
hyperlight-guest-bin = { git = "https://github.com/hyperlight-dev/hyperlight", rev = "620339aa95d508e8cbd1d38b4374f09090aade7b" }
16+
hyperlight-guest = { git = "https://github.com/hyperlight-dev/hyperlight", rev = "620339aa95d508e8cbd1d38b4374f09090aade7b" }
17+
hyperlight-host = { git = "https://github.com/hyperlight-dev/hyperlight", rev = "620339aa95d508e8cbd1d38b4374f09090aade7b", default-features = false, features = ["executable_heap", "init-paging"] }
1818
hyperlight-js = { version = "0.1.1", path = "src/hyperlight-js" }
1919
hyperlight-js-runtime = { version = "0.1.1", path = "src/hyperlight-js-runtime" }
2020

src/hyperlight-js/benches/benchmarks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ fn handle_events_benchmark(c: &mut Criterion) {
182182
let start = Instant::now();
183183
let _ =
184184
loaded_js_sandbox.handle_event("function1", event.to_string(), Some(gc));
185-
loaded_js_sandbox.restore(&snapshot).unwrap();
185+
loaded_js_sandbox.restore(snapshot.clone()).unwrap();
186186
elapsed += start.elapsed();
187187
}
188188
}

src/hyperlight-js/examples/interrupt/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ fn main() -> Result<()> {
9999

100100
// Demonstrate recovery from poisoned state
101101
println!("\n📸 Restoring sandbox from snapshot...");
102-
loaded_sandbox.restore(&snapshot)?;
102+
loaded_sandbox.restore(snapshot)?;
103103

104104
println!("🔒 Poisoned after restore: {}", loaded_sandbox.poisoned());
105105
assert!(

src/hyperlight-js/src/sandbox/js_sandbox.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
22
use std::fmt::Debug;
3+
use std::sync::Arc;
34

45
use hyperlight_host::sandbox::snapshot::Snapshot;
56
use hyperlight_host::{new_error, MultiUseSandbox, Result};
@@ -15,7 +16,7 @@ pub struct JSSandbox {
1516
handlers: HashMap<String, Script>,
1617
// Snapshot of state before any handlers are added.
1718
// This is used to restore state back to a neutral JSSandbox.
18-
snapshot: Snapshot,
19+
snapshot: Arc<Snapshot>,
1920
// metric drop guard to manage sandbox metric
2021
_metric_guard: SandboxMetricsGuard<JSSandbox>,
2122
}
@@ -33,8 +34,11 @@ impl JSSandbox {
3334
}
3435

3536
/// Creates a new `JSSandbox` from a `MultiUseSandbox` and a `Snapshot` of state before any handlers were added.
36-
pub(crate) fn from_loaded(mut loaded: MultiUseSandbox, snapshot: Snapshot) -> Result<Self> {
37-
loaded.restore(&snapshot)?;
37+
pub(crate) fn from_loaded(
38+
mut loaded: MultiUseSandbox,
39+
snapshot: Arc<Snapshot>,
40+
) -> Result<Self> {
41+
loaded.restore(snapshot.clone())?;
3842
Ok(Self {
3943
inner: loaded,
4044
handlers: HashMap::new(),

src/hyperlight-js/src/sandbox/loaded_js_sandbox.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct LoadedJSSandbox {
2121
inner: MultiUseSandbox,
2222
// Snapshot of state before the sandbox was loaded and before any handlers were added.
2323
// This is used to restore state back to a JSSandbox.
24-
snapshot: Snapshot,
24+
snapshot: Arc<Snapshot>,
2525
// metric drop guard to manage sandbox metric
2626
_metric_guard: SandboxMetricsGuard<LoadedJSSandbox>,
2727
}
@@ -42,7 +42,7 @@ impl Drop for MonitorTask {
4242

4343
impl LoadedJSSandbox {
4444
#[instrument(err(Debug), skip_all, level=Level::INFO)]
45-
pub(super) fn new(inner: MultiUseSandbox, snapshot: Snapshot) -> Result<LoadedJSSandbox> {
45+
pub(super) fn new(inner: MultiUseSandbox, snapshot: Arc<Snapshot>) -> Result<LoadedJSSandbox> {
4646
metrics::counter!(METRIC_SANDBOX_LOADS).increment(1);
4747
Ok(LoadedJSSandbox {
4848
inner,
@@ -92,13 +92,13 @@ impl LoadedJSSandbox {
9292
/// Take a snapshot of the the current state of the sandbox.
9393
/// This can be used to restore the state of the sandbox later.
9494
#[instrument(err(Debug), skip_all, level=Level::DEBUG)]
95-
pub fn snapshot(&mut self) -> Result<Snapshot> {
95+
pub fn snapshot(&mut self) -> Result<Arc<Snapshot>> {
9696
self.inner.snapshot()
9797
}
9898

9999
/// Restore the state of the sandbox to a previous snapshot.
100100
#[instrument(err(Debug), skip_all, level=Level::DEBUG)]
101-
pub fn restore(&mut self, snapshot: &Snapshot) -> Result<()> {
101+
pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()> {
102102
self.inner.restore(snapshot)?;
103103
Ok(())
104104
}
@@ -402,7 +402,7 @@ mod tests {
402402
assert_eq!(response_json["count"], 3);
403403

404404
// Restore the snapshot
405-
loaded_js_sandbox.restore(&snapshot).unwrap();
405+
loaded_js_sandbox.restore(snapshot.clone()).unwrap();
406406

407407
// Handle the event again, should reset to initial state
408408
let result = loaded_js_sandbox
@@ -432,7 +432,7 @@ mod tests {
432432
.unwrap_err();
433433

434434
// restore to snapshot before unload/reload
435-
reloaded_js_sandbox.restore(&snapshot).unwrap();
435+
reloaded_js_sandbox.restore(snapshot.clone()).unwrap();
436436
// handler should be available again
437437
let result = reloaded_js_sandbox
438438
.handle_event("handler", get_static_counter_event(), gc)

src/hyperlight-js/src/sandbox/sandbox_builder.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#[cfg(target_os = "linux")]
22
use std::time::Duration;
33

4-
use hyperlight_host::sandbox::{is_hypervisor_present, SandboxConfiguration};
5-
use hyperlight_host::{GuestBinary, HyperlightError, Result};
4+
use hyperlight_host::sandbox::SandboxConfiguration;
5+
use hyperlight_host::{is_hypervisor_present, GuestBinary, HyperlightError, Result};
66

77
use super::proto_js_sandbox::ProtoJSSandbox;
88
use crate::HostPrintFn;
@@ -13,16 +13,35 @@ pub struct SandboxBuilder {
1313
host_print_fn: Option<HostPrintFn>,
1414
}
1515

16-
const MIN_STACK_SIZE: u64 = 256 * 1024;
17-
// The minimum heap size is 4096KB.
16+
/// The minimum scratch size for the JS runtime sandbox.
17+
///
18+
/// The scratch region provides writable physical memory for:
19+
/// - I/O buffers (input + output data)
20+
/// - Page table copies (proportional to snapshot size — our ~13 MB guest
21+
/// binary + heap produce ~72 KiB of page tables)
22+
/// - Dynamically allocated pages (GDT/IDT, stack growth, Copy-on-Write
23+
/// resolution during QuickJS initialisation)
24+
/// - Exception stack and metadata (2 pages at the top)
25+
///
26+
/// Hyperlight's default scratch (288 KiB) is far too small for the JS
27+
/// runtime guest: after fixed overheads there are only ~44 free pages,
28+
/// which are exhausted during init. 1 MiB (0x10_0000) matches
29+
/// hyperlight's own "large guest" test configuration and gives
30+
/// comfortable headroom.
31+
const MIN_SCRATCH_SIZE: usize = 0x10_0000; // 1 MiB
32+
33+
/// The minimum heap size is 4 MiB. The QuickJS engine needs a
34+
/// reasonable amount of heap during initialisation for builtins,
35+
/// global objects, and the bytecode compiler. This lives in the
36+
/// identity-mapped snapshot region (NOT scratch).
1837
const MIN_HEAP_SIZE: u64 = 4096 * 1024;
1938

2039
impl SandboxBuilder {
2140
/// Create a new SandboxBuilder
2241
pub fn new() -> Self {
2342
let mut config = SandboxConfiguration::default();
24-
config.set_stack_size(MIN_STACK_SIZE);
2543
config.set_heap_size(MIN_HEAP_SIZE);
44+
config.set_scratch_size(MIN_SCRATCH_SIZE);
2645

2746
Self {
2847
config,
@@ -52,13 +71,14 @@ impl SandboxBuilder {
5271
self
5372
}
5473

55-
/// Set the guest stack size
56-
/// This is the size of the stack that code executing in the guest can use.
57-
/// If this value is too small then the guest will fail with a stack overflow error
58-
/// The default value (and minimum) is set to the value of the MIN_STACK_SIZE const.
59-
pub fn with_guest_stack_size(mut self, guest_stack_size: u64) -> Self {
60-
if guest_stack_size > MIN_STACK_SIZE {
61-
self.config.set_stack_size(guest_stack_size);
74+
/// Set the guest scratch size in bytes.
75+
/// The scratch region provides writable memory for the guest, including the
76+
/// dynamically-sized stack. Increase this if your guest code needs deep
77+
/// recursion or large local variables.
78+
/// Values smaller than the default (288KiB) are ignored.
79+
pub fn with_guest_scratch_size(mut self, guest_scratch_size: usize) -> Self {
80+
if guest_scratch_size > MIN_SCRATCH_SIZE {
81+
self.config.set_scratch_size(guest_scratch_size);
6282
}
6383
self
6484
}

src/hyperlight-js/tests/monitors.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ fn wall_clock_monitor_sandbox_recovers_with_restore() {
9797
assert!(loaded.poisoned(), "Should be poisoned after kill");
9898

9999
// Restore from snapshot
100-
loaded.restore(&snapshot).unwrap();
100+
loaded.restore(snapshot.clone()).unwrap();
101101
assert!(!loaded.poisoned(), "Should not be poisoned after restore");
102102

103103
// Should be able to run again
@@ -162,7 +162,7 @@ fn cpu_time_monitor_sandbox_recovers_with_restore() {
162162
assert!(loaded.poisoned(), "Should be poisoned after kill");
163163

164164
// Restore from snapshot
165-
loaded.restore(&snapshot).unwrap();
165+
loaded.restore(snapshot.clone()).unwrap();
166166
assert!(!loaded.poisoned(), "Should not be poisoned after restore");
167167

168168
// Should be able to run again
@@ -237,7 +237,7 @@ fn tuple_monitor_sandbox_recovers_with_restore() {
237237
assert!(loaded.poisoned(), "Should be poisoned after kill");
238238

239239
// Restore and verify recovery
240-
loaded.restore(&snapshot).unwrap();
240+
loaded.restore(snapshot.clone()).unwrap();
241241
assert!(!loaded.poisoned(), "Should not be poisoned after restore");
242242

243243
let monitor2 = (

src/hyperlight-js/tests/termination.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ fn handle_termination() -> Result<()> {
9292
);
9393

9494
// Restore the sandbox from snapshot
95-
loaded_sandbox.restore(&snapshot)?;
95+
loaded_sandbox.restore(snapshot)?;
9696

9797
// Verify sandbox is no longer poisoned after restore
9898
assert!(

src/js-host-api/README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ Creates and configures a new sandbox.
5050

5151
**Methods:**
5252
- `setHeapSize(bytes: number)``this` — Set guest heap size (must be > 0, chainable)
53-
- `setStackSize(bytes: number)``this` — Set guest stack size (must be > 0, chainable)
53+
- `setScratchSize(bytes: number)``this` — Set guest scratch size, includes stack (must be > 0, chainable)
5454
- `setInputBufferSize(bytes: number)``this` — Set guest input buffer size (must be > 0, chainable)
5555
- `setOutputBufferSize(bytes: number)``this` — Set guest output buffer size (must be > 0, chainable)
5656
- `build()``Promise<ProtoJSSandbox>` — Builds a proto sandbox ready to load the JavaScript runtime
5757

5858
```javascript
5959
const builder = new SandboxBuilder()
6060
.setHeapSize(8 * 1024 * 1024)
61-
.setStackSize(512 * 1024);
61+
.setScratchSize(1024 * 1024);
6262
const protoSandbox = await builder.build();
6363
```
6464

@@ -237,9 +237,8 @@ All errors thrown by the API include a `code` property for programmatic handling
237237
|------|---------|
238238
| `ERR_INVALID_ARG` | Bad argument (empty handler name, zero timeout, etc.) |
239239
| `ERR_CONSUMED` | Object already consumed (e.g., calling `loadRuntime()` twice) |
240-
| `ERR_POISONED` | Sandbox is in an inconsistent state (after timeout kill, guest abort, etc.) — restore from snapshot or unload |
240+
| `ERR_POISONED` | Sandbox is in an inconsistent state (after timeout kill, guest abort, stack overflow, etc.) — restore from snapshot or unload |
241241
| `ERR_CANCELLED` | Execution was cancelled (by monitor timeout or manual `kill()`) |
242-
| `ERR_STACK_OVERFLOW` | Guest code caused a stack overflow |
243242
| `ERR_GUEST_ABORT` | Guest code aborted |
244243
| `ERR_INTERNAL` | Unexpected internal error |
245244

@@ -251,8 +250,8 @@ try {
251250
case 'ERR_CANCELLED':
252251
console.log('Execution was cancelled');
253252
break;
254-
case 'ERR_STACK_OVERFLOW':
255-
console.log('Stack overflow in guest code');
253+
case 'ERR_POISONED':
254+
console.log('Sandbox is poisoned (e.g. stack overflow, timeout)');
256255
break;
257256
default:
258257
console.log(`Unexpected error [${error.code}]: ${error.message}`);

0 commit comments

Comments
 (0)