From b55f3b073b86888d38afd133072b1d6adae67d14 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:17:17 -0700 Subject: [PATCH 1/2] Version bumps Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .github/workflows/cargo-publish.yml | 2 +- .github/workflows/dep_benchmarks.yml | 2 +- .github/workflows/dep_build.yml | 2 +- .github/workflows/npm-publish.yml | 2 +- Cargo.lock | 47 ++++++------ Cargo.toml | 16 ++-- Justfile | 2 +- .../fixtures/extended_runtime/Cargo.lock | 59 ++++++++------ src/hyperlight-js/Cargo.toml | 2 +- .../npm/linux-x64-gnu/package.json | 2 +- .../npm/linux-x64-musl/package.json | 2 +- .../npm/win32-x64-msvc/package.json | 2 +- src/js-host-api/package-lock.json | 76 ++----------------- src/js-host-api/package.json | 8 +- 14 files changed, 86 insertions(+), 138 deletions(-) diff --git a/.github/workflows/cargo-publish.yml b/.github/workflows/cargo-publish.yml index fe8e766a..7a194ed2 100644 --- a/.github/workflows/cargo-publish.yml +++ b/.github/workflows/cargo-publish.yml @@ -47,7 +47,7 @@ jobs: # Ensures just is installed using setup wokflow to ensure just version consistency - name: Hyperlight setup - uses: hyperlight-dev/ci-setup-workflow@v1.9.0 + uses: hyperlight-dev/ci-setup-workflow@v1.10.0 with: rust-toolchain: "1.90" just-version: "1.51" diff --git a/.github/workflows/dep_benchmarks.yml b/.github/workflows/dep_benchmarks.yml index 03f5d626..8d0c91e7 100644 --- a/.github/workflows/dep_benchmarks.yml +++ b/.github/workflows/dep_benchmarks.yml @@ -54,7 +54,7 @@ jobs: with: fetch-depth: 0 - - uses: hyperlight-dev/ci-setup-workflow@v1.9.0 + - uses: hyperlight-dev/ci-setup-workflow@v1.10.0 with: rust-toolchain: "1.90" just-version: "1.51" diff --git a/.github/workflows/dep_build.yml b/.github/workflows/dep_build.yml index 3f68d814..4bb82c50 100644 --- a/.github/workflows/dep_build.yml +++ b/.github/workflows/dep_build.yml @@ -59,7 +59,7 @@ jobs: with: fetch-depth: 0 - - uses: hyperlight-dev/ci-setup-workflow@v1.9.0 + - uses: hyperlight-dev/ci-setup-workflow@v1.10.0 with: rust-toolchain: "1.90" just-version: "1.51" diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 972940ad..8dabdff6 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -64,7 +64,7 @@ jobs: fetch-depth: 0 - name: Hyperlight setup - uses: hyperlight-dev/ci-setup-workflow@v1.9.0 + uses: hyperlight-dev/ci-setup-workflow@v1.10.0 with: rust-toolchain: "1.90" just-version: "1.51" diff --git a/Cargo.lock b/Cargo.lock index d5365460..ccd3830c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,9 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cargo-hyperlight" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5605a328ba56e38027ec2ab1d6cd490c942c3908a92cb2b36b3c775d5bb22f6" +checksum = "c52f1c9d4b6173aefb66fdc2e3e112ddb359fe2fbdbaafe1b9ba25f7ac578b39" dependencies = [ "anyhow", "clap", @@ -1598,9 +1598,9 @@ dependencies = [ [[package]] name = "hyperlight-common" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436dac5cc08da3db27de38b174a9b22498ab6443aaffd83b97f66a5d2f1d74cc" +checksum = "3ad5576d6ef947dc2822c2f5038ddff8325c158ddef4cbabf9d0b125294cdb0f" dependencies = [ "anyhow", "bitflags 2.13.0", @@ -1618,9 +1618,9 @@ dependencies = [ [[package]] name = "hyperlight-guest" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb2e6548e2b837400a7b27a5ed6a6570c28801253e6ec80959700dd63925c40" +checksum = "816f673f3f8ef6d5703796bf92927b85177ad1baac6f391551a88451a9e46a7e" dependencies = [ "anyhow", "flatbuffers", @@ -1632,9 +1632,9 @@ dependencies = [ [[package]] name = "hyperlight-guest-bin" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeccde6490e76aefe2e8ca17e1a225d2da3338f020f17533b8ee0cee7cf9a11" +checksum = "9c2d0ea739be89822992706da9dc6812ce359b654b328efd6141ba1549751907" dependencies = [ "buddy_system_allocator", "flatbuffers", @@ -1651,21 +1651,21 @@ dependencies = [ [[package]] name = "hyperlight-guest-macro" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620ed77c5c07027c63a6a741da8a9a2462a56e660691da715c3700cb281a3d23" +checksum = "875d03a9e4d302f29d8feb57faff937f80c6f69ec9d31b32f41c41721144e25d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] name = "hyperlight-guest-tracing" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b39160c2001db5c79916d946d04a601e4e1f2827e2c785d477b31e48f5e514" +checksum = "8f43a3d5d3fa245ce0fccee8a48ad7d94ec479e0898fc8f91fd440a1d65830c6" dependencies = [ "hyperlight-common", "spin 0.12.2", @@ -1675,14 +1675,16 @@ dependencies = [ [[package]] name = "hyperlight-host" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef3edc7875e70a3b5cc291a17f78cfc352e24c9a665a5a1bbdbc46ada3df43b5" +checksum = "d85dcf5ae7a578e1b09bdeb76cc3b3ca8e6e57d6395d2f57204aec45c1bbbde9" dependencies = [ "anyhow", + "bindgen", "bitflags 2.13.0", "blake3", "bytemuck", + "cc", "cfg-if", "cfg_aliases", "chrono", @@ -1706,6 +1708,7 @@ dependencies = [ "oci-spec", "opentelemetry", "page_size", + "parking_lot", "rand 0.10.2", "rust-embed", "serde", @@ -1727,7 +1730,7 @@ dependencies = [ [[package]] name = "hyperlight-js" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "cargo-hyperlight", @@ -1771,11 +1774,11 @@ dependencies = [ [[package]] name = "hyperlight-js-common" -version = "0.3.3" +version = "0.4.0" [[package]] name = "hyperlight-js-runtime" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "base64", @@ -1801,9 +1804,9 @@ dependencies = [ [[package]] name = "hyperlight-libc" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c258315b4c0ec4aae172c4ff31b34590559fb6699c144f779be2246672b3147" +checksum = "108dd0e7335821a2ef0dfa375c08236c3302a117045822e2aa68c64147f8e895" dependencies = [ "anyhow", "bindgen", @@ -2029,7 +2032,7 @@ dependencies = [ [[package]] name = "js-host-api" -version = "0.3.3" +version = "0.4.0" dependencies = [ "arc-swap", "hyperlight-js", @@ -3742,7 +3745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index d4d717ee..345d4a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["src/hyperlight-js", "src/js-host-api", "src/hyperlight-js-runtime", exclude = ["src/hyperlight-js-runtime/tests/fixtures"] [workspace.package] -version = "0.3.3" +version = "0.4.0" edition = "2024" rust-version = "1.89" license = "Apache-2.0" @@ -12,14 +12,14 @@ repository = "https://github.com/hyperlight-dev/hyperlight-js" readme = "README.md" [workspace.dependencies] -hyperlight-guest-bin = { version = "0.16.0", features = ["libc"] } -hyperlight-host = { version = "0.16.0", default-features = false } -hyperlight-common = { version = "0.16.0", default-features = false } -hyperlight-guest = { version = "0.16.0" } +hyperlight-guest-bin = { version = "0.17.0", features = ["libc"] } +hyperlight-host = { version = "0.17.0", default-features = false } +hyperlight-common = { version = "0.17.0", default-features = false } +hyperlight-guest = { version = "0.17.0" } -hyperlight-js = { version = "0.3.3", path = "src/hyperlight-js" } -hyperlight-js-runtime = { version = "0.3.3", path = "src/hyperlight-js-runtime" } -hyperlight-js-common = { version = "0.3.3", path = "src/hyperlight-js-common" } +hyperlight-js = { version = "0.4.0", path = "src/hyperlight-js" } +hyperlight-js-runtime = { version = "0.4.0", path = "src/hyperlight-js-runtime" } +hyperlight-js-common = { version = "0.4.0", path = "src/hyperlight-js-common" } [profile.dev] panic = "abort" diff --git a/Justfile b/Justfile index aac81250..2be09134 100644 --- a/Justfile +++ b/Justfile @@ -19,7 +19,7 @@ export HYPERLIGHT_CFLAGS := \ export CMAKE_GENERATOR := if os() == "windows" { "Ninja" } else { "" } ensure-tools: - cargo install cargo-hyperlight --locked + cargo install cargo-hyperlight --version 0.1.14 --locked # Check if npm is installed, install automatically if missing (Linux) [private] diff --git a/src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.lock b/src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.lock index cecb844d..ec572674 100644 --- a/src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.lock +++ b/src/hyperlight-js-runtime/tests/fixtures/extended_runtime/Cargo.lock @@ -107,7 +107,7 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn", + "syn 2.0.117", ] [[package]] @@ -151,7 +151,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -227,7 +227,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "hyperlight-common" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436dac5cc08da3db27de38b174a9b22498ab6443aaffd83b97f66a5d2f1d74cc" +checksum = "3ad5576d6ef947dc2822c2f5038ddff8325c158ddef4cbabf9d0b125294cdb0f" dependencies = [ "anyhow", "bitflags", @@ -449,9 +449,9 @@ dependencies = [ [[package]] name = "hyperlight-guest" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb2e6548e2b837400a7b27a5ed6a6570c28801253e6ec80959700dd63925c40" +checksum = "816f673f3f8ef6d5703796bf92927b85177ad1baac6f391551a88451a9e46a7e" dependencies = [ "anyhow", "flatbuffers", @@ -462,9 +462,9 @@ dependencies = [ [[package]] name = "hyperlight-guest-bin" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeccde6490e76aefe2e8ca17e1a225d2da3338f020f17533b8ee0cee7cf9a11" +checksum = "9c2d0ea739be89822992706da9dc6812ce359b654b328efd6141ba1549751907" dependencies = [ "buddy_system_allocator", "flatbuffers", @@ -481,21 +481,21 @@ dependencies = [ [[package]] name = "hyperlight-guest-macro" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620ed77c5c07027c63a6a741da8a9a2462a56e660691da715c3700cb281a3d23" +checksum = "875d03a9e4d302f29d8feb57faff937f80c6f69ec9d31b32f41c41721144e25d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] name = "hyperlight-guest-tracing" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b39160c2001db5c79916d946d04a601e4e1f2827e2c785d477b31e48f5e514" +checksum = "8f43a3d5d3fa245ce0fccee8a48ad7d94ec479e0898fc8f91fd440a1d65830c6" dependencies = [ "hyperlight-common", "spin 0.12.0", @@ -505,11 +505,11 @@ dependencies = [ [[package]] name = "hyperlight-js-common" -version = "0.3.3" +version = "0.4.0" [[package]] name = "hyperlight-js-runtime" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "base64", @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "hyperlight-libc" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c258315b4c0ec4aae172c4ff31b34590559fb6699c144f779be2246672b3147" +checksum = "108dd0e7335821a2ef0dfa375c08236c3302a117045822e2aa68c64147f8e895" dependencies = [ "anyhow", "bindgen", @@ -613,7 +613,7 @@ checksum = "32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -679,7 +679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -783,7 +783,7 @@ dependencies = [ "proc-macro2", "quote", "rquickjs-core", - "syn", + "syn 2.0.117", ] [[package]] @@ -850,7 +850,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -933,6 +933,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -950,7 +961,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1003,7 +1014,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/src/hyperlight-js/Cargo.toml b/src/hyperlight-js/Cargo.toml index 62ae4c58..ab76a7c0 100644 --- a/src/hyperlight-js/Cargo.toml +++ b/src/hyperlight-js/Cargo.toml @@ -36,7 +36,7 @@ libc = { version = "0.2", optional = true } windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_System_Registry", "Win32_System_WindowsProgramming"], optional = true } [build-dependencies] -cargo-hyperlight = "0.1.11" +cargo-hyperlight = "0.1.14" serde_json = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } diff --git a/src/js-host-api/npm/linux-x64-gnu/package.json b/src/js-host-api/npm/linux-x64-gnu/package.json index ed305574..7e69ef3f 100644 --- a/src/js-host-api/npm/linux-x64-gnu/package.json +++ b/src/js-host-api/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlight-dev/js-host-api-linux-x64-gnu", - "version": "0.3.3", + "version": "0.4.0", "os": [ "linux" ], diff --git a/src/js-host-api/npm/linux-x64-musl/package.json b/src/js-host-api/npm/linux-x64-musl/package.json index ccae588a..39058a91 100644 --- a/src/js-host-api/npm/linux-x64-musl/package.json +++ b/src/js-host-api/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlight-dev/js-host-api-linux-x64-musl", - "version": "0.3.3", + "version": "0.4.0", "os": [ "linux" ], diff --git a/src/js-host-api/npm/win32-x64-msvc/package.json b/src/js-host-api/npm/win32-x64-msvc/package.json index 828e4b61..a16ce33b 100644 --- a/src/js-host-api/npm/win32-x64-msvc/package.json +++ b/src/js-host-api/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlight-dev/js-host-api-win32-x64-msvc", - "version": "0.3.3", + "version": "0.4.0", "os": [ "win32" ], diff --git a/src/js-host-api/package-lock.json b/src/js-host-api/package-lock.json index d8340382..b747fcee 100644 --- a/src/js-host-api/package-lock.json +++ b/src/js-host-api/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hyperlight-dev/js-host-api", - "version": "0.3.3", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hyperlight-dev/js-host-api", - "version": "0.3.3", + "version": "0.4.0", "license": "Apache-2.0", "devDependencies": { "@eslint/js": "^10.0.1", @@ -19,9 +19,9 @@ "node": ">= 18" }, "optionalDependencies": { - "@hyperlight-dev/js-host-api-linux-x64-gnu": "0.3.3", - "@hyperlight-dev/js-host-api-linux-x64-musl": "0.3.3", - "@hyperlight-dev/js-host-api-win32-x64-msvc": "0.3.3" + "@hyperlight-dev/js-host-api-linux-x64-gnu": "0.4.0", + "@hyperlight-dev/js-host-api-linux-x64-musl": "0.4.0", + "@hyperlight-dev/js-host-api-win32-x64-msvc": "0.4.0" } }, "node_modules/@emnapi/core": { @@ -253,60 +253,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@hyperlight-dev/js-host-api-linux-x64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@hyperlight-dev/js-host-api-linux-x64-gnu/-/js-host-api-linux-x64-gnu-0.3.3.tgz", - "integrity": "sha512-DO26tGQoaiwM1iWikPdjMVPxm4Ge6IWtdfCoZLGnntEnlRSCoa6DB3/fwW8ihZM++vaoWm1WzW8TBoOe8qxHuA==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@hyperlight-dev/js-host-api-linux-x64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@hyperlight-dev/js-host-api-linux-x64-musl/-/js-host-api-linux-x64-musl-0.3.3.tgz", - "integrity": "sha512-8PnXByjWmxmTFGDT8cwaQD8oaz3JBeD6WF5F2p/ncO32eakl7Ll490J9McR9Mcp6rX1RmgrIRuiT9b9U+VDgKQ==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@hyperlight-dev/js-host-api-win32-x64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@hyperlight-dev/js-host-api-win32-x64-msvc/-/js-host-api-win32-x64-msvc-0.3.3.tgz", - "integrity": "sha512-5TQ5g9/qLBPIYtq8lY2xPsevBiWJ1k8CFykDU2kc2u4dRRRF/XYs/TH7BWjQnE9+rSJrwg4YxDRzt6CoMAp19g==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 18" - } - }, "node_modules/@inquirer/ansi": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", @@ -1529,9 +1475,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1549,9 +1492,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1569,9 +1509,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1589,9 +1526,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/src/js-host-api/package.json b/src/js-host-api/package.json index 1c6334b6..b56c57eb 100644 --- a/src/js-host-api/package.json +++ b/src/js-host-api/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlight-dev/js-host-api", - "version": "0.3.3", + "version": "0.4.0", "description": "Node.js API bindings for Hyperlight JS", "main": "lib.js", "module": "lib.mjs", @@ -35,9 +35,9 @@ }, "license": "Apache-2.0", "optionalDependencies": { - "@hyperlight-dev/js-host-api-linux-x64-gnu": "0.3.3", - "@hyperlight-dev/js-host-api-linux-x64-musl": "0.3.3", - "@hyperlight-dev/js-host-api-win32-x64-msvc": "0.3.3" + "@hyperlight-dev/js-host-api-linux-x64-gnu": "0.4.0", + "@hyperlight-dev/js-host-api-linux-x64-musl": "0.4.0", + "@hyperlight-dev/js-host-api-win32-x64-msvc": "0.4.0" }, "devDependencies": { "@eslint/js": "^10.0.1", From d01fef58e6e904aef8cb194e2013d1c2f180e3d6 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:40:28 -0700 Subject: [PATCH 2/2] Do the changes Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .gitignore | 1 + Justfile | 2 +- .../examples/execution_stats/main.rs | 4 +- src/hyperlight-js/examples/interrupt/main.rs | 23 +++- src/hyperlight-js/src/lib.rs | 2 +- src/hyperlight-js/src/sandbox/js_sandbox.rs | 15 ++- .../src/sandbox/loaded_js_sandbox.rs | 19 ++- .../src/sandbox/sandbox_builder.rs | 2 +- src/hyperlight-js/tests/monitors.rs | 65 ++++++--- src/hyperlight-js/tests/termination.rs | 6 +- src/js-host-api/README.md | 5 +- src/js-host-api/src/lib.rs | 124 ++++++++++++++---- src/js-host-api/tests/sandbox.test.js | 4 +- src/js-host-api/tests/timeout.test.js | 3 + 14 files changed, 201 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 5bd05d62..a189c545 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target +node_modules/ .idea \ No newline at end of file diff --git a/Justfile b/Justfile index 2be09134..aac81250 100644 --- a/Justfile +++ b/Justfile @@ -19,7 +19,7 @@ export HYPERLIGHT_CFLAGS := \ export CMAKE_GENERATOR := if os() == "windows" { "Ninja" } else { "" } ensure-tools: - cargo install cargo-hyperlight --version 0.1.14 --locked + cargo install cargo-hyperlight --locked # Check if npm is installed, install automatically if missing (Linux) [private] diff --git a/src/hyperlight-js/examples/execution_stats/main.rs b/src/hyperlight-js/examples/execution_stats/main.rs index ad75968e..42511e97 100644 --- a/src/hyperlight-js/examples/execution_stats/main.rs +++ b/src/hyperlight-js/examples/execution_stats/main.rs @@ -173,7 +173,7 @@ fn main() -> Result<()> { " šŸ’€ Terminated by: {} āœ…", stats.terminated_by.unwrap_or("(unknown)") ); - println!(" šŸ”’ Poisoned: {}", loaded.poisoned()); + println!(" šŸ”’ Poisoned: {}", loaded.status().is_poisoned()); } } @@ -181,7 +181,7 @@ fn main() -> Result<()> { loaded.restore(snapshot.clone())?; println!( " šŸ“ø Restored from snapshot — poisoned: {}", - loaded.poisoned() + loaded.status().is_poisoned() ); } diff --git a/src/hyperlight-js/examples/interrupt/main.rs b/src/hyperlight-js/examples/interrupt/main.rs index 073e379b..2b8c6087 100644 --- a/src/hyperlight-js/examples/interrupt/main.rs +++ b/src/hyperlight-js/examples/interrupt/main.rs @@ -17,7 +17,7 @@ limitations under the License. //! //! This example shows how to: //! 1. Use `interrupt_handle().kill()` to terminate long-running handlers -//! 2. Check the `poisoned()` state after interruption +//! 2. Check the sandbox status after interruption //! 3. Use `snapshot()` and `restore()` to recover from poisoned state //! //! Run with: cargo run --example interrupt @@ -57,9 +57,12 @@ fn main() -> Result<()> { let mut loaded_sandbox = sandbox.get_loaded_sandbox()?; // Verify sandbox is not poisoned initially - println!("šŸ”’ Initial poisoned state: {}", loaded_sandbox.poisoned()); + println!( + "šŸ”’ Initial poisoned state: {}", + loaded_sandbox.status().is_poisoned() + ); assert!( - !loaded_sandbox.poisoned(), + !loaded_sandbox.status().is_poisoned(), "Sandbox should not be poisoned initially" ); @@ -100,9 +103,12 @@ fn main() -> Result<()> { } Err(hyperlight_js::HyperlightError::ExecutionCanceledByHost()) => { println!("\nāœ… Handler was properly interrupted!"); - println!("šŸ”’ Poisoned after interrupt: {}", loaded_sandbox.poisoned()); + println!( + "šŸ”’ Poisoned after interrupt: {}", + loaded_sandbox.status().is_poisoned() + ); assert!( - loaded_sandbox.poisoned(), + loaded_sandbox.status().is_poisoned(), "Sandbox should be poisoned after interruption" ); } @@ -116,9 +122,12 @@ fn main() -> Result<()> { println!("\nšŸ“ø Restoring sandbox from snapshot..."); loaded_sandbox.restore(snapshot)?; - println!("šŸ”’ Poisoned after restore: {}", loaded_sandbox.poisoned()); + println!( + "šŸ”’ Poisoned after restore: {}", + loaded_sandbox.status().is_poisoned() + ); assert!( - !loaded_sandbox.poisoned(), + !loaded_sandbox.status().is_poisoned(), "Sandbox should not be poisoned after restore" ); diff --git a/src/hyperlight-js/src/lib.rs b/src/hyperlight-js/src/lib.rs index ec391973..a1928fa0 100644 --- a/src/hyperlight-js/src/lib.rs +++ b/src/hyperlight-js/src/lib.rs @@ -72,7 +72,7 @@ pub type ReturnType = hyperlight_host::func::ReturnType; /// A snapshot of sandbox state that can be used to restore it later. pub use hyperlight_host::sandbox::snapshot::Snapshot; /// Configuration for sandbox resource limits and behavior. -pub use hyperlight_host::sandbox::SandboxConfiguration; +pub use hyperlight_host::sandbox::{SandboxConfiguration, SandboxStatus}; /// Module resolution and loading functionality. pub use resolver::{FileMetadata, FileSystem, FileSystemEmbedded, ResolveError}; /// Execution statistics from the most recent guest function call. diff --git a/src/hyperlight-js/src/sandbox/js_sandbox.rs b/src/hyperlight-js/src/sandbox/js_sandbox.rs index a97961bf..980bd83c 100644 --- a/src/hyperlight-js/src/sandbox/js_sandbox.rs +++ b/src/hyperlight-js/src/sandbox/js_sandbox.rs @@ -18,7 +18,7 @@ use std::fmt::Debug; use std::sync::Arc; use hyperlight_host::sandbox::snapshot::Snapshot; -use hyperlight_host::{new_error, MultiUseSandbox, Result}; +use hyperlight_host::{new_error, MultiUseSandbox, Result, SandboxStatus}; use tracing::{instrument, Level}; use super::loaded_js_sandbox::LoadedJSSandbox; @@ -277,13 +277,14 @@ impl JSSandbox { } /// Returns whether the sandbox is currently poisoned. - /// - /// A poisoned sandbox is in an inconsistent state due to the guest not running to completion. - /// This can happen when guest execution is interrupted (e.g., via `InterruptHandle::kill()`), - /// when the guest panics, or when memory violations occur. - /// + #[deprecated(since = "0.4.0", note = "use status().is_poisoned()")] pub fn poisoned(&self) -> bool { - self.inner.poisoned() + self.inner.status().is_poisoned() + } + + /// Returns the sandbox lifecycle status. + pub fn status(&self) -> SandboxStatus { + self.inner.status() } #[cfg(test)] diff --git a/src/hyperlight-js/src/sandbox/loaded_js_sandbox.rs b/src/hyperlight-js/src/sandbox/loaded_js_sandbox.rs index c63ecaa0..dc04f036 100644 --- a/src/hyperlight-js/src/sandbox/loaded_js_sandbox.rs +++ b/src/hyperlight-js/src/sandbox/loaded_js_sandbox.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use hyperlight_host::hypervisor::InterruptHandle; use hyperlight_host::sandbox::snapshot::Snapshot; use hyperlight_host::HyperlightError::{self, JsonConversionFailure}; -use hyperlight_host::{MultiUseSandbox, Result}; +use hyperlight_host::{MultiUseSandbox, Result, SandboxStatus}; use tokio::task::JoinHandle; use tracing::{instrument, Level}; @@ -193,15 +193,14 @@ impl LoadedJSSandbox { } /// Returns whether the sandbox is currently poisoned. - /// - /// A poisoned sandbox is in an inconsistent state due to the guest not running to completion. - /// This can happen when guest execution is interrupted (e.g., via `InterruptHandle::kill()`), - /// when the guest panics, or when memory violations occur. - /// - /// When poisoned, most operations will fail with `PoisonedSandbox` error. - /// Use `restore()` with a snapshot or `unload()` to recover from a poisoned state. + #[deprecated(since = "0.4.0", note = "use status().is_poisoned()")] pub fn poisoned(&self) -> bool { - self.inner.poisoned() + self.inner.status().is_poisoned() + } + + /// Returns the sandbox lifecycle status. + pub fn status(&self) -> SandboxStatus { + self.inner.status() } /// Handles an event with execution monitoring. @@ -648,7 +647,7 @@ mod tests { // Sandbox should NOT be poisoned - we never ran the handler assert!( - !loaded.poisoned(), + !loaded.status().is_poisoned(), "Sandbox should not be poisoned when monitor fails to start" ); } diff --git a/src/hyperlight-js/src/sandbox/sandbox_builder.rs b/src/hyperlight-js/src/sandbox/sandbox_builder.rs index e82ac649..cffa161e 100644 --- a/src/hyperlight-js/src/sandbox/sandbox_builder.rs +++ b/src/hyperlight-js/src/sandbox/sandbox_builder.rs @@ -171,7 +171,7 @@ impl SandboxBuilder { if !is_hypervisor_present() { return Err(HyperlightError::NoHypervisorFound()); } - let guest_binary = GuestBinary::Buffer(super::JSRUNTIME); + let guest_binary = GuestBinary::Buffer(super::JSRUNTIME.to_vec()); let proto_js_sandbox = ProtoJSSandbox::new(guest_binary, Some(self.config), self.host_print_fn)?; Ok(proto_js_sandbox) diff --git a/src/hyperlight-js/tests/monitors.rs b/src/hyperlight-js/tests/monitors.rs index 89590ce1..0ce08364 100644 --- a/src/hyperlight-js/tests/monitors.rs +++ b/src/hyperlight-js/tests/monitors.rs @@ -66,7 +66,10 @@ fn wall_clock_monitor_completes_fast_handler() { assert!(result.is_ok(), "Fast handler should complete: {:?}", result); let output = result.unwrap(); assert!(output.contains("counter"), "Should have counter in output"); - assert!(!loaded.poisoned(), "Sandbox should not be poisoned"); + assert!( + !loaded.status().is_poisoned(), + "Sandbox should not be poisoned" + ); } #[test] @@ -95,7 +98,10 @@ fn wall_clock_monitor_kills_slow_handler() { // Result should be error since handler was killed assert!(result.is_err(), "Killed handler should return error"); - assert!(loaded.poisoned(), "Sandbox should be poisoned after kill"); + assert!( + loaded.status().is_poisoned(), + "Sandbox should be poisoned after kill" + ); } #[test] @@ -109,11 +115,17 @@ fn wall_clock_monitor_sandbox_recovers_with_restore() { let event = r#"{"runtime": 5000}"#; let _ = loaded.handle_event_with_monitor("handler", event.to_string(), &monitor, None); - assert!(loaded.poisoned(), "Should be poisoned after kill"); + assert!( + loaded.status().is_poisoned(), + "Should be poisoned after kill" + ); // Restore from snapshot loaded.restore(snapshot.clone()).unwrap(); - assert!(!loaded.poisoned(), "Should not be poisoned after restore"); + assert!( + !loaded.status().is_poisoned(), + "Should not be poisoned after restore" + ); // Should be able to run again let monitor2 = WallClockMonitor::new(Duration::from_secs(5)).unwrap(); @@ -135,7 +147,10 @@ fn cpu_time_monitor_completes_fast_handler() { assert!(result.is_ok(), "Fast handler should complete: {:?}", result); let output = result.unwrap(); assert!(output.contains("counter"), "Should have counter in output"); - assert!(!loaded.poisoned(), "Sandbox should not be poisoned"); + assert!( + !loaded.status().is_poisoned(), + "Sandbox should not be poisoned" + ); } #[test] @@ -160,7 +175,10 @@ fn cpu_time_monitor_kills_cpu_intensive_handler() { // Result should be error since handler was killed assert!(result.is_err(), "Killed handler should return error"); - assert!(loaded.poisoned(), "Sandbox should be poisoned after kill"); + assert!( + loaded.status().is_poisoned(), + "Sandbox should be poisoned after kill" + ); } #[test] @@ -174,11 +192,17 @@ fn cpu_time_monitor_sandbox_recovers_with_restore() { let event = r#"{"runtime": 5000}"#; let _ = loaded.handle_event_with_monitor("handler", event.to_string(), &monitor, None); - assert!(loaded.poisoned(), "Should be poisoned after kill"); + assert!( + loaded.status().is_poisoned(), + "Should be poisoned after kill" + ); // Restore from snapshot loaded.restore(snapshot.clone()).unwrap(); - assert!(!loaded.poisoned(), "Should not be poisoned after restore"); + assert!( + !loaded.status().is_poisoned(), + "Should not be poisoned after restore" + ); // Should be able to run again let monitor2 = CpuTimeMonitor::new(Duration::from_secs(5)).unwrap(); @@ -210,7 +234,7 @@ fn tuple_monitor_kills_cpu_intensive_handler() { // CPU monitor should fire first (tight loop ā‰ˆ 100% CPU utilisation) assert!(result.is_err(), "Should be killed by CPU monitor"); - assert!(loaded.poisoned(), "Sandbox should be poisoned"); + assert!(loaded.status().is_poisoned(), "Sandbox should be poisoned"); assert!( elapsed < Duration::from_secs(3), "CPU monitor should fire well before wall-clock, took {:?}", @@ -233,7 +257,10 @@ fn tuple_monitor_completes_fast_handler() { let result = loaded.handle_event_with_monitor("handler", event.to_string(), &monitor, None); assert!(result.is_ok(), "Fast handler should complete: {:?}", result); - assert!(!loaded.poisoned(), "Sandbox should not be poisoned"); + assert!( + !loaded.status().is_poisoned(), + "Sandbox should not be poisoned" + ); } #[test] @@ -249,11 +276,17 @@ fn tuple_monitor_sandbox_recovers_with_restore() { ); let event = r#"{"runtime": 10000}"#; let _ = loaded.handle_event_with_monitor("handler", event.to_string(), &monitor, None); - assert!(loaded.poisoned(), "Should be poisoned after kill"); + assert!( + loaded.status().is_poisoned(), + "Should be poisoned after kill" + ); // Restore and verify recovery loaded.restore(snapshot.clone()).unwrap(); - assert!(!loaded.poisoned(), "Should not be poisoned after restore"); + assert!( + !loaded.status().is_poisoned(), + "Should not be poisoned after restore" + ); let monitor2 = ( CpuTimeMonitor::new(Duration::from_secs(5)).unwrap(), @@ -309,7 +342,7 @@ fn tuple_with_failing_monitor_is_fail_closed() { ); // Sandbox should NOT be poisoned — we never ran the handler assert!( - !loaded.poisoned(), + !loaded.status().is_poisoned(), "Sandbox should not be poisoned when monitor fails to start" ); } @@ -326,12 +359,12 @@ fn monitor_reuse_across_calls() { // First call let result1 = loaded.handle_event_with_monitor("handler", event.to_string(), &monitor, None); assert!(result1.is_ok(), "First call should succeed: {:?}", result1); - assert!(!loaded.poisoned()); + assert!(!loaded.status().is_poisoned()); // Second call with same monitor instance let result2 = loaded.handle_event_with_monitor("handler", event.to_string(), &monitor, None); assert!(result2.is_ok(), "Second call should succeed: {:?}", result2); - assert!(!loaded.poisoned()); + assert!(!loaded.status().is_poisoned()); } /// Single-element tuple monitors should work identically to a bare monitor. @@ -347,7 +380,7 @@ fn single_element_tuple_monitor() { let elapsed = start.elapsed(); assert!(result.is_err(), "Should be killed by 1-tuple monitor"); - assert!(loaded.poisoned(), "Sandbox should be poisoned"); + assert!(loaded.status().is_poisoned(), "Sandbox should be poisoned"); assert!( elapsed < Duration::from_secs(2), "Should terminate quickly, took {:?}", diff --git a/src/hyperlight-js/tests/termination.rs b/src/hyperlight-js/tests/termination.rs index 61ecbbbc..66092acb 100644 --- a/src/hyperlight-js/tests/termination.rs +++ b/src/hyperlight-js/tests/termination.rs @@ -51,7 +51,7 @@ fn handle_termination() -> Result<()> { // Verify sandbox is not poisoned before we start assert!( - !loaded_sandbox.poisoned(), + !loaded_sandbox.status().is_poisoned(), "Sandbox should not be poisoned initially" ); @@ -102,7 +102,7 @@ fn handle_termination() -> Result<()> { // Verify sandbox is poisoned after interruption assert!( - loaded_sandbox.poisoned(), + loaded_sandbox.status().is_poisoned(), "Sandbox should be poisoned after interruption" ); @@ -111,7 +111,7 @@ fn handle_termination() -> Result<()> { // Verify sandbox is no longer poisoned after restore assert!( - !loaded_sandbox.poisoned(), + !loaded_sandbox.status().is_poisoned(), "Sandbox should not be poisoned after restore" ); diff --git a/src/js-host-api/README.md b/src/js-host-api/README.md index 2c0bcec4..18bfa605 100644 --- a/src/js-host-api/README.md +++ b/src/js-host-api/README.md @@ -141,7 +141,8 @@ A sandbox with handlers loaded, ready to process events. **Properties:** - `interruptHandle` → `InterruptHandle` — Gets a handle to interrupt/kill handler execution (getter, not a method) -- `poisoned` → `boolean` — Whether the sandbox is in a poisoned (inconsistent) state +- `status` → `SandboxStatus` — Lifecycle status: `ready`, `poisoned`, or `unrecoverable` +- `poisoned` → `boolean` — Deprecated compatibility alias for `status === 'poisoned'` - `lastCallStats` → `CallStats | null` — Execution statistics from the most recent `callHandler()` call (see [CallStats](#callstats-) below) ```javascript @@ -288,7 +289,7 @@ const snapshot = await loaded.snapshot(); // ... handler gets killed, sandbox is poisoned ... await loaded.restore(snapshot); -console.log(loaded.poisoned); // false — back to normal +console.log(loaded.status); // "ready" ``` ### Error Codes diff --git a/src/js-host-api/src/lib.rs b/src/js-host-api/src/lib.rs index 049c518c..5bce9a77 100644 --- a/src/js-host-api/src/lib.rs +++ b/src/js-host-api/src/lib.rs @@ -13,14 +13,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; use arc_swap::ArcSwapOption; use hyperlight_js::{ CpuTimeMonitor, ExecutionStats, HyperlightError, InterruptHandle, JSSandbox, LoadedJSSandbox, - ProtoJSSandbox, SandboxBuilder, Script, Snapshot, WallClockMonitor, + ProtoJSSandbox, SandboxBuilder, SandboxStatus as HyperlightSandboxStatus, Script, Snapshot, + WallClockMonitor, }; use napi::bindgen_prelude::{FromNapiValue, JsValuesTupleIntoVec, Promise, ToNapiValue}; use napi::sys::{napi_env, napi_value}; @@ -68,13 +69,59 @@ use tokio::sync::{oneshot, Mutex as AsyncMutex, OwnedMappedMutexGuard, OwnedMute // to be callable during that time: // // - `interruptHandle` — the whole point is to `kill()` a *running* handler -// - `poisoned` — callers want to check state without blocking the event loop +// - `status` — callers want to check state without blocking the event loop // // Both are cloned out of the `LoadedJSSandbox` at construction time and stored -// as separate `Arc` fields that never touch the Mutex. The `poisoned_flag` -// (`AtomicBool`) is updated inside every `spawn_blocking` closure where we +// as separate `Arc` fields that never touch the Mutex. The `status` +// (`AtomicU8`) is updated inside every `spawn_blocking` closure where we // already hold the lock, so it stays in sync without extra contention. +/// The lifecycle status of a sandbox. +#[napi(string_enum)] +#[derive(Clone, Copy)] +pub enum SandboxStatus { + /// The sandbox can execute guest operations. + #[napi(value = "ready")] + Ready, + /// The sandbox requires a successful restore before further use. + #[napi(value = "poisoned")] + Poisoned, + /// The sandbox cannot be used and must be discarded. + #[napi(value = "unrecoverable")] + Unrecoverable, +} + +impl SandboxStatus { + const fn from_hyperlight(status: HyperlightSandboxStatus) -> Self { + match status { + HyperlightSandboxStatus::Ready => Self::Ready, + HyperlightSandboxStatus::Poisoned => Self::Poisoned, + HyperlightSandboxStatus::Unrecoverable => Self::Unrecoverable, + } + } + + const fn as_u8(self) -> u8 { + match self { + Self::Ready => 0, + Self::Poisoned => 1, + Self::Unrecoverable => 2, + } + } + + fn from_u8(status: u8) -> Self { + match status { + 0 => Self::Ready, + 1 => Self::Poisoned, + 2 => Self::Unrecoverable, + _ => unreachable!("invalid sandbox status"), + } + } + + const fn is_poisoned(self) -> bool { + matches!(self, Self::Poisoned) + } +} + // ── Error codes ────────────────────────────────────────────────────── // // ## Why we embed error codes in the message string @@ -1712,28 +1759,38 @@ impl JSSandboxWrapper { }) .await .map_err(join_error)??; - // Grab the interrupt handle and poisoned state before moving behind the Mutex. + // Grab the interrupt handle and status before moving behind the Mutex. // These are stored separately so they never contend with the inner lock — // callers can read them even while guest code is executing on a background thread. let interrupt = loaded_sandbox.interrupt_handle(); - let poisoned_flag = Arc::new(AtomicBool::new(loaded_sandbox.poisoned())); + let status = Arc::new(AtomicU8::new( + SandboxStatus::from_hyperlight(loaded_sandbox.status()).as_u8(), + )); Ok(LoadedJSSandboxWrapper { inner: Arc::new(AsyncMutex::new(Some(loaded_sandbox))), interrupt, - poisoned_flag, + status, last_call_stats: Arc::new(ArcSwapOption::empty()), disposed_flag: Arc::new(AtomicBool::new(false)), executing_flag: Arc::new(AtomicBool::new(false)), }) } + /// The sandbox lifecycle status. + #[napi(getter)] + pub fn status(&self) -> napi::Result { + self.with_inner_ref(|sandbox| Ok(SandboxStatus::from_hyperlight(sandbox.status()))) + } + /// Whether the sandbox is in a poisoned (inconsistent) state. /// /// A poisoned sandbox has had its guest execution interrupted or /// aborted. Most operations will fail with an `ERR_POISONED` error code. + /// + /// @deprecated Use `status` instead. #[napi(getter)] pub fn poisoned(&self) -> napi::Result { - self.with_inner_ref(|sandbox| Ok(sandbox.poisoned())) + self.status().map(SandboxStatus::is_poisoned) } /// Eagerly release the underlying sandbox resources. @@ -1779,16 +1836,16 @@ pub struct LoadedJSSandboxWrapper { /// See the module-level architecture comment for the full rationale. interrupt: Arc, - /// Tracks poisoned state **outside** the Mutex for lock-free reads. + /// Tracks lifecycle status **outside** the Mutex for lock-free reads. /// - /// The `poisoned` getter is a sync napi property (not async). If it tried + /// The `status` getter is a sync napi property (not async). If it tried /// to acquire the Mutex while `call_handler()` is running, it would block /// the Node.js event loop until guest execution finishes. /// /// Updated via `Ordering::Release` inside every `spawn_blocking` closure /// (where we already hold the lock), read via `Ordering::Acquire` in the /// getter. See the module-level architecture comment for the full rationale. - poisoned_flag: Arc, + status: Arc, /// Execution statistics from the most recent `callHandler()` call. /// @@ -1972,7 +2029,7 @@ impl LoadedJSSandboxWrapper { ))); } - let poisoned_flag = self.poisoned_flag.clone(); + let status = self.status.clone(); let last_call_stats_store = self.last_call_stats.clone(); let gc = options.gc; let wall_clock_timeout_ms = options.wall_clock_timeout_ms; @@ -2025,9 +2082,12 @@ impl LoadedJSSandboxWrapper { .map_err(to_napi_error) } }; - // Update poisoned flag while we hold the lock — keeps the getter + // Update status while we hold the lock — keeps the getter // lock-free so it never blocks the Node.js event loop. - poisoned_flag.store(sandbox.poisoned(), Ordering::Release); + status.store( + SandboxStatus::from_hyperlight(sandbox.status()).as_u8(), + Ordering::Release, + ); // Copy execution stats while we still hold the lock. last_call_stats_store.store( @@ -2095,6 +2155,20 @@ impl LoadedJSSandboxWrapper { }) } + /// The sandbox lifecycle status. + /// + /// This getter is lock-free and never blocks the event loop, even while a + /// handler is executing on a background thread. + /// + /// @throws `ERR_CONSUMED` if the sandbox has been consumed via `dispose()` or `unload()` + #[napi(getter)] + pub fn status(&self) -> napi::Result { + if self.disposed_flag.load(Ordering::Acquire) { + return Err(consumed_error("LoadedJSSandbox")); + } + Ok(SandboxStatus::from_u8(self.status.load(Ordering::Acquire))) + } + /// Whether the sandbox is in a poisoned (inconsistent) state. /// /// A sandbox becomes poisoned when guest execution is interrupted @@ -2109,13 +2183,11 @@ impl LoadedJSSandboxWrapper { /// - `restore(snapshot)` — revert to a captured state /// - `unload()` — discard handlers and start fresh /// + /// @deprecated Use `status` instead. /// @throws `ERR_CONSUMED` if the sandbox has been consumed via `dispose()` or `unload()` #[napi(getter)] pub fn poisoned(&self) -> napi::Result { - if self.disposed_flag.load(Ordering::Acquire) { - return Err(consumed_error("LoadedJSSandbox")); - } - Ok(self.poisoned_flag.load(Ordering::Acquire)) + self.status().map(SandboxStatus::is_poisoned) } /// Execution statistics from the most recent `callHandler()` call. @@ -2164,12 +2236,15 @@ impl LoadedJSSandboxWrapper { /// @throws If already consumed #[napi] pub async fn snapshot(&self) -> napi::Result { - let poisoned_flag = self.poisoned_flag.clone(); + let status = self.status.clone(); let snapshot = self .with_blocking_inner(move |mut sandbox| { let result = sandbox.snapshot().map_err(to_napi_error); - poisoned_flag.store(sandbox.poisoned(), Ordering::Release); + status.store( + SandboxStatus::from_hyperlight(sandbox.status()).as_u8(), + Ordering::Release, + ); result }) .await?; @@ -2188,11 +2263,14 @@ impl LoadedJSSandboxWrapper { #[napi] pub async fn restore(&self, snapshot: &SnapshotWrapper) -> napi::Result<()> { let snap = snapshot.inner.clone(); - let poisoned_flag = self.poisoned_flag.clone(); + let status = self.status.clone(); self.with_blocking_inner(move |mut sandbox| { let result = sandbox.restore(snap).map_err(to_napi_error); - poisoned_flag.store(sandbox.poisoned(), Ordering::Release); + status.store( + SandboxStatus::from_hyperlight(sandbox.status()).as_u8(), + Ordering::Release, + ); result }) .await?; diff --git a/src/js-host-api/tests/sandbox.test.js b/src/js-host-api/tests/sandbox.test.js index 31c6969f..13c47188 100644 --- a/src/js-host-api/tests/sandbox.test.js +++ b/src/js-host-api/tests/sandbox.test.js @@ -130,7 +130,8 @@ describe('JSSandbox', () => { expect(loaded).toBeDefined(); }); - it('should report poisoned state', () => { + it('should report ready status', () => { + expect(sandbox.status).toBe('ready'); expect(sandbox.poisoned).toBe(false); }); @@ -195,6 +196,7 @@ describe('LoadedJSSandbox', () => { }); it('should not be poisoned initially', () => { + expect(loaded.status).toBe('ready'); expect(loaded.poisoned).toBe(false); }); diff --git a/src/js-host-api/tests/timeout.test.js b/src/js-host-api/tests/timeout.test.js index 8fce6374..69c196d0 100644 --- a/src/js-host-api/tests/timeout.test.js +++ b/src/js-host-api/tests/timeout.test.js @@ -70,6 +70,7 @@ describe('Wall Clock Timeout', () => { // Should have been killed around 500ms, not 4000ms expect(elapsed).toBeLessThan(2000); expect(elapsed).toBeGreaterThan(300); + expect(loaded.status).toBe('poisoned'); expect(loaded.poisoned).toBe(true); }); @@ -86,11 +87,13 @@ describe('Wall Clock Timeout', () => { 'ERR_CANCELLED' ); + expect(loaded.status).toBe('poisoned'); expect(loaded.poisoned).toBe(true); // Restore from snapshot await loaded.restore(snapshot); + expect(loaded.status).toBe('ready'); expect(loaded.poisoned).toBe(false); // Should be able to use the sandbox again