Skip to content

Commit 5cd2ab9

Browse files
authored
Updates for the latest hypleright version (#24)
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent 528f655 commit 5cd2ab9

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
@@ -197,7 +197,7 @@ fn handle_events_benchmark(c: &mut Criterion) {
197197
let start = Instant::now();
198198
let _ =
199199
loaded_js_sandbox.handle_event("function1", event.to_string(), Some(gc));
200-
loaded_js_sandbox.restore(&snapshot).unwrap();
200+
loaded_js_sandbox.restore(snapshot.clone()).unwrap();
201201
elapsed += start.elapsed();
202202
}
203203
}

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

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

115115
// Demonstrate recovery from poisoned state
116116
println!("\n📸 Restoring sandbox from snapshot...");
117-
loaded_sandbox.restore(&snapshot)?;
117+
loaded_sandbox.restore(snapshot)?;
118118

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

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ limitations under the License.
1515
*/
1616
use std::collections::HashMap;
1717
use std::fmt::Debug;
18+
use std::sync::Arc;
1819

1920
use hyperlight_host::sandbox::snapshot::Snapshot;
2021
use hyperlight_host::{new_error, MultiUseSandbox, Result};
@@ -30,7 +31,7 @@ pub struct JSSandbox {
3031
handlers: HashMap<String, Script>,
3132
// Snapshot of state before any handlers are added.
3233
// This is used to restore state back to a neutral JSSandbox.
33-
snapshot: Snapshot,
34+
snapshot: Arc<Snapshot>,
3435
// metric drop guard to manage sandbox metric
3536
_metric_guard: SandboxMetricsGuard<JSSandbox>,
3637
}
@@ -48,8 +49,11 @@ impl JSSandbox {
4849
}
4950

5051
/// Creates a new `JSSandbox` from a `MultiUseSandbox` and a `Snapshot` of state before any handlers were added.
51-
pub(crate) fn from_loaded(mut loaded: MultiUseSandbox, snapshot: Snapshot) -> Result<Self> {
52-
loaded.restore(&snapshot)?;
52+
pub(crate) fn from_loaded(
53+
mut loaded: MultiUseSandbox,
54+
snapshot: Arc<Snapshot>,
55+
) -> Result<Self> {
56+
loaded.restore(snapshot.clone())?;
5357
Ok(Self {
5458
inner: loaded,
5559
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
@@ -36,7 +36,7 @@ pub struct LoadedJSSandbox {
3636
inner: MultiUseSandbox,
3737
// Snapshot of state before the sandbox was loaded and before any handlers were added.
3838
// This is used to restore state back to a JSSandbox.
39-
snapshot: Snapshot,
39+
snapshot: Arc<Snapshot>,
4040
// metric drop guard to manage sandbox metric
4141
_metric_guard: SandboxMetricsGuard<LoadedJSSandbox>,
4242
}
@@ -57,7 +57,7 @@ impl Drop for MonitorTask {
5757

5858
impl LoadedJSSandbox {
5959
#[instrument(err(Debug), skip_all, level=Level::INFO)]
60-
pub(super) fn new(inner: MultiUseSandbox, snapshot: Snapshot) -> Result<LoadedJSSandbox> {
60+
pub(super) fn new(inner: MultiUseSandbox, snapshot: Arc<Snapshot>) -> Result<LoadedJSSandbox> {
6161
metrics::counter!(METRIC_SANDBOX_LOADS).increment(1);
6262
Ok(LoadedJSSandbox {
6363
inner,
@@ -107,13 +107,13 @@ impl LoadedJSSandbox {
107107
/// Take a snapshot of the the current state of the sandbox.
108108
/// This can be used to restore the state of the sandbox later.
109109
#[instrument(err(Debug), skip_all, level=Level::DEBUG)]
110-
pub fn snapshot(&mut self) -> Result<Snapshot> {
110+
pub fn snapshot(&mut self) -> Result<Arc<Snapshot>> {
111111
self.inner.snapshot()
112112
}
113113

114114
/// Restore the state of the sandbox to a previous snapshot.
115115
#[instrument(err(Debug), skip_all, level=Level::DEBUG)]
116-
pub fn restore(&mut self, snapshot: &Snapshot) -> Result<()> {
116+
pub fn restore(&mut self, snapshot: Arc<Snapshot>) -> Result<()> {
117117
self.inner.restore(snapshot)?;
118118
Ok(())
119119
}
@@ -417,7 +417,7 @@ mod tests {
417417
assert_eq!(response_json["count"], 3);
418418

419419
// Restore the snapshot
420-
loaded_js_sandbox.restore(&snapshot).unwrap();
420+
loaded_js_sandbox.restore(snapshot.clone()).unwrap();
421421

422422
// Handle the event again, should reset to initial state
423423
let result = loaded_js_sandbox
@@ -447,7 +447,7 @@ mod tests {
447447
.unwrap_err();
448448

449449
// restore to snapshot before unload/reload
450-
reloaded_js_sandbox.restore(&snapshot).unwrap();
450+
reloaded_js_sandbox.restore(snapshot.clone()).unwrap();
451451
// handler should be available again
452452
let result = reloaded_js_sandbox
453453
.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
@@ -16,8 +16,8 @@ limitations under the License.
1616
#[cfg(target_os = "linux")]
1717
use std::time::Duration;
1818

19-
use hyperlight_host::sandbox::{is_hypervisor_present, SandboxConfiguration};
20-
use hyperlight_host::{GuestBinary, HyperlightError, Result};
19+
use hyperlight_host::sandbox::SandboxConfiguration;
20+
use hyperlight_host::{is_hypervisor_present, GuestBinary, HyperlightError, Result};
2121

2222
use super::proto_js_sandbox::ProtoJSSandbox;
2323
use crate::HostPrintFn;
@@ -28,16 +28,35 @@ pub struct SandboxBuilder {
2828
host_print_fn: Option<HostPrintFn>,
2929
}
3030

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

3554
impl SandboxBuilder {
3655
/// Create a new SandboxBuilder
3756
pub fn new() -> Self {
3857
let mut config = SandboxConfiguration::default();
39-
config.set_stack_size(MIN_STACK_SIZE);
4058
config.set_heap_size(MIN_HEAP_SIZE);
59+
config.set_scratch_size(MIN_SCRATCH_SIZE);
4160

4261
Self {
4362
config,
@@ -67,13 +86,14 @@ impl SandboxBuilder {
6786
self
6887
}
6988

70-
/// Set the guest stack size
71-
/// This is the size of the stack that code executing in the guest can use.
72-
/// If this value is too small then the guest will fail with a stack overflow error
73-
/// The default value (and minimum) is set to the value of the MIN_STACK_SIZE const.
74-
pub fn with_guest_stack_size(mut self, guest_stack_size: u64) -> Self {
75-
if guest_stack_size > MIN_STACK_SIZE {
76-
self.config.set_stack_size(guest_stack_size);
89+
/// Set the guest scratch size in bytes.
90+
/// The scratch region provides writable memory for the guest, including the
91+
/// dynamically-sized stack. Increase this if your guest code needs deep
92+
/// recursion or large local variables.
93+
/// Values smaller than the default (288KiB) are ignored.
94+
pub fn with_guest_scratch_size(mut self, guest_scratch_size: usize) -> Self {
95+
if guest_scratch_size > MIN_SCRATCH_SIZE {
96+
self.config.set_scratch_size(guest_scratch_size);
7797
}
7898
self
7999
}

src/hyperlight-js/tests/monitors.rs

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

114114
// Restore from snapshot
115-
loaded.restore(&snapshot).unwrap();
115+
loaded.restore(snapshot.clone()).unwrap();
116116
assert!(!loaded.poisoned(), "Should not be poisoned after restore");
117117

118118
// Should be able to run again
@@ -177,7 +177,7 @@ fn cpu_time_monitor_sandbox_recovers_with_restore() {
177177
assert!(loaded.poisoned(), "Should be poisoned after kill");
178178

179179
// Restore from snapshot
180-
loaded.restore(&snapshot).unwrap();
180+
loaded.restore(snapshot.clone()).unwrap();
181181
assert!(!loaded.poisoned(), "Should not be poisoned after restore");
182182

183183
// Should be able to run again
@@ -252,7 +252,7 @@ fn tuple_monitor_sandbox_recovers_with_restore() {
252252
assert!(loaded.poisoned(), "Should be poisoned after kill");
253253

254254
// Restore and verify recovery
255-
loaded.restore(&snapshot).unwrap();
255+
loaded.restore(snapshot.clone()).unwrap();
256256
assert!(!loaded.poisoned(), "Should not be poisoned after restore");
257257

258258
let monitor2 = (

src/hyperlight-js/tests/termination.rs

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

109109
// Restore the sandbox from snapshot
110-
loaded_sandbox.restore(&snapshot)?;
110+
loaded_sandbox.restore(snapshot)?;
111111

112112
// Verify sandbox is no longer poisoned after restore
113113
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)