kobako 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
//! Per-path cache of pre-instantiated wasmtime artifacts.
|
|
2
2
|
//!
|
|
3
|
-
//! The
|
|
4
|
-
//!
|
|
5
|
-
//!
|
|
6
|
-
//!
|
|
7
|
-
//! the `
|
|
8
|
-
//! Caching the resolved `InstancePre` per path — together with the ABI
|
|
9
|
-
//! version check it guards (`crate::abi`) — takes both off the
|
|
10
|
-
//! `Driver::new` hot path.
|
|
3
|
+
//! The host imports (`crate::imports`) and their type-check against the
|
|
4
|
+
//! compiled Module are identical for every `Kobako::Runtime` on the same
|
|
5
|
+
//! Guest Binary. Caching the resolved `InstancePre` per path — together
|
|
6
|
+
//! with the ABI version check it guards (`crate::abi`) — takes both off
|
|
7
|
+
//! the `Driver::new` hot path.
|
|
11
8
|
//!
|
|
12
9
|
//! Concurrency: the same as `crate::cache` — the Mutex covers lookup and
|
|
13
10
|
//! insert only, and a concurrent miss builds an equivalent `InstancePre`.
|
|
@@ -15,19 +12,24 @@
|
|
|
15
12
|
use std::collections::HashMap;
|
|
16
13
|
use std::path::{Path, PathBuf};
|
|
17
14
|
use std::sync::{Mutex, OnceLock};
|
|
15
|
+
use std::time::Duration;
|
|
18
16
|
|
|
19
|
-
use wasmtime::
|
|
20
|
-
use wasmtime_wasi::p1;
|
|
17
|
+
use wasmtime::InstancePre;
|
|
21
18
|
|
|
22
|
-
use crate::cache::
|
|
19
|
+
use crate::cache::load_module;
|
|
23
20
|
use crate::invocation::Invocation;
|
|
24
|
-
use crate::{abi,
|
|
21
|
+
use crate::{abi, imports, trap};
|
|
25
22
|
use kobako_runtime::error::SetupError;
|
|
26
23
|
|
|
27
24
|
static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocation>>>> =
|
|
28
25
|
OnceLock::new();
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
/// The probe runs only on the first load of a path, so only that load's
|
|
28
|
+
/// `setup_timeout` bounds it; a path already verified is not set up again.
|
|
29
|
+
pub(crate) fn load_instance_pre(
|
|
30
|
+
path: &Path,
|
|
31
|
+
setup_timeout: Option<Duration>,
|
|
32
|
+
) -> Result<InstancePre<Invocation>, SetupError> {
|
|
31
33
|
let cache = INSTANCE_PRE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
|
32
34
|
|
|
33
35
|
if let Some(pre) = cache
|
|
@@ -39,47 +41,17 @@ pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>
|
|
|
39
41
|
return Ok(pre);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
let module =
|
|
43
|
-
let linker =
|
|
44
|
+
let module = load_module(path)?;
|
|
45
|
+
let linker = imports::linker()?;
|
|
44
46
|
let pre = linker
|
|
45
47
|
.instantiate_pre(&module)
|
|
46
48
|
.map_err(trap::instantiate_err)?;
|
|
47
49
|
// Verify before the insert, so a rejected artifact is never
|
|
48
50
|
// remembered as a usable template.
|
|
49
|
-
abi::verify(&pre)?;
|
|
51
|
+
abi::verify(&pre, setup_timeout)?;
|
|
50
52
|
cache
|
|
51
53
|
.lock()
|
|
52
54
|
.expect("instance_pre cache mutex poisoned")
|
|
53
55
|
.insert(path.to_path_buf(), pre.clone());
|
|
54
56
|
Ok(pre)
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
fn build_linker() -> Result<Linker<Invocation>, SetupError> {
|
|
58
|
-
let mut linker: Linker<Invocation> = Linker::new(shared_engine()?);
|
|
59
|
-
|
|
60
|
-
// Wire the wasmtime-wasi preview1 WASI imports. Routes guest fd 1/2
|
|
61
|
-
// to the MemoryOutputPipes set up before each run via
|
|
62
|
-
// `Driver::invoke`. The closure pulls a `&mut WasiP1Ctx` out of
|
|
63
|
-
// Invocation; the panic semantics live inside `Invocation::wasi_mut`
|
|
64
|
-
// so the wiring stays honest about its precondition.
|
|
65
|
-
p1::add_to_linker_sync(&mut linker, |state: &mut Invocation| state.wasi_mut())
|
|
66
|
-
.map_err(|e| SetupError::Dead(format!("failed to set up the WASI runtime: {e}")))?;
|
|
67
|
-
|
|
68
|
-
// `__kobako_upcall`, `(ptr: i32, len: i32) -> i64` per
|
|
69
|
-
// docs/wire/abi.md § ABI Signatures; `dispatch::handle` owns what it
|
|
70
|
-
// answers.
|
|
71
|
-
linker
|
|
72
|
-
.func_wrap(
|
|
73
|
-
"env",
|
|
74
|
-
"__kobako_upcall",
|
|
75
|
-
|mut caller: Caller<'_, Invocation>,
|
|
76
|
-
req_ptr: i32,
|
|
77
|
-
req_len: i32|
|
|
78
|
-
-> wasmtime::Result<i64> {
|
|
79
|
-
dispatch::handle(&mut caller, req_ptr, req_len)
|
|
80
|
-
},
|
|
81
|
-
)
|
|
82
|
-
.map_err(|e| SetupError::Dead(format!("failed to set up the host callback bridge: {e}")))?;
|
|
83
|
-
|
|
84
|
-
Ok(linker)
|
|
85
|
-
}
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
//! Owned as the data of each per-invocation `wasmtime::Store`
|
|
5
5
|
//! and threaded through every host import —
|
|
6
6
|
//! the `__kobako_upcall` dispatcher reads the bound dispatch handler,
|
|
7
|
-
//!
|
|
8
|
-
//!
|
|
9
|
-
//! export call.
|
|
7
|
+
//! and the WASI imports read the invocation's `Wasi` context, which the
|
|
8
|
+
//! Store is created with.
|
|
10
9
|
//!
|
|
11
10
|
//! The slot also carries the per-invocation wall-clock deadline and the
|
|
12
11
|
//! memory cap `MemoryLimiter`, both read from the wasmtime
|
|
@@ -16,53 +15,56 @@
|
|
|
16
15
|
use std::sync::Arc;
|
|
17
16
|
use std::time::{Duration, Instant};
|
|
18
17
|
|
|
19
|
-
use wasmtime_wasi::p1::WasiP1Ctx;
|
|
20
|
-
use wasmtime_wasi::p2::pipe::MemoryOutputPipe;
|
|
21
|
-
|
|
22
18
|
use kobako_runtime::dispatch::DispatchHandler;
|
|
19
|
+
use kobako_runtime::endpoint::{Calls, MemoryCap, ABANDONED};
|
|
23
20
|
|
|
24
21
|
use crate::exports::Exports;
|
|
25
22
|
use crate::limiter::MemoryLimiter;
|
|
23
|
+
use crate::wasi::Wasi;
|
|
26
24
|
|
|
27
25
|
/// Per-invocation host state, owned by the invocation's own Store.
|
|
28
26
|
/// Threaded through every host import callback.
|
|
29
27
|
///
|
|
30
|
-
/// All field access is mediated by methods on this type — the
|
|
31
|
-
///
|
|
32
|
-
/// `Invocation::
|
|
33
|
-
/// `Invocation::
|
|
34
|
-
///
|
|
35
|
-
/// `Invocation::stderr_bytes`. The fields are private so the mutation
|
|
36
|
-
/// surface stays narrow.
|
|
28
|
+
/// All field access is mediated by methods on this type — the `Wasi`
|
|
29
|
+
/// context arrives with the invocation, the dispatch handler is set once
|
|
30
|
+
/// via `Invocation::bind_on_dispatch`, and the captures are read back
|
|
31
|
+
/// through `Invocation::wasi` after the run. The fields are private so
|
|
32
|
+
/// the mutation surface stays narrow.
|
|
37
33
|
pub(crate) struct Invocation {
|
|
38
|
-
wasi:
|
|
39
|
-
|
|
40
|
-
stderr_pipe: Option<MemoryOutputPipe>,
|
|
41
|
-
on_dispatch: Option<Arc<dyn DispatchHandler>>,
|
|
34
|
+
wasi: Wasi,
|
|
35
|
+
calls: Calls<wasmtime::Error>,
|
|
42
36
|
deadline: Option<Instant>,
|
|
43
37
|
limiter: MemoryLimiter,
|
|
44
38
|
wall_entry: Option<Instant>,
|
|
45
39
|
wall_time: Duration,
|
|
46
|
-
reentry_trap: Option<wasmtime::Error>,
|
|
47
40
|
exports: Option<Exports>,
|
|
41
|
+
hostcall_fuel: usize,
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
impl Invocation {
|
|
51
|
-
pub(crate) fn new(memory_limit: Option<usize
|
|
45
|
+
pub(crate) fn new(memory_limit: Option<usize>, wasi: Wasi) -> Self {
|
|
52
46
|
Self {
|
|
53
|
-
wasi
|
|
54
|
-
|
|
55
|
-
stderr_pipe: None,
|
|
56
|
-
on_dispatch: None,
|
|
47
|
+
wasi,
|
|
48
|
+
calls: Calls::default(),
|
|
57
49
|
deadline: None,
|
|
58
|
-
limiter: MemoryLimiter::new(memory_limit),
|
|
50
|
+
limiter: MemoryLimiter(MemoryCap::new(memory_limit)),
|
|
59
51
|
wall_entry: None,
|
|
60
52
|
wall_time: Duration::ZERO,
|
|
61
|
-
reentry_trap: None,
|
|
62
53
|
exports: None,
|
|
54
|
+
hostcall_fuel: 0,
|
|
63
55
|
}
|
|
64
56
|
}
|
|
65
57
|
|
|
58
|
+
/// How much of the guest's memory the call under way may have the host
|
|
59
|
+
/// copy or walk, as wasmtime hands each host call.
|
|
60
|
+
pub(crate) fn hostcall_fuel(&self) -> usize {
|
|
61
|
+
self.hostcall_fuel
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub(crate) fn set_hostcall_fuel(&mut self, fuel: usize) {
|
|
65
|
+
self.hostcall_fuel = fuel;
|
|
66
|
+
}
|
|
67
|
+
|
|
66
68
|
pub(crate) fn set_exports(&mut self, exports: Exports) {
|
|
67
69
|
self.exports = Some(exports);
|
|
68
70
|
}
|
|
@@ -73,61 +75,28 @@ impl Invocation {
|
|
|
73
75
|
self.exports.clone()
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
/// The
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.reentry_trap.get_or_insert(trap);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/// Once a callback into the guest has trapped, the guest is past
|
|
83
|
-
/// resuming.
|
|
84
|
-
pub(crate) fn reentry_trapped(&self) -> bool {
|
|
85
|
-
self.reentry_trap.is_some()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
pub(crate) fn take_reentry_trap(&mut self) -> Option<wasmtime::Error> {
|
|
89
|
-
self.reentry_trap.take()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
pub(crate) fn install_wasi(
|
|
93
|
-
&mut self,
|
|
94
|
-
wasi: WasiP1Ctx,
|
|
95
|
-
stdout: MemoryOutputPipe,
|
|
96
|
-
stderr: MemoryOutputPipe,
|
|
97
|
-
) {
|
|
98
|
-
self.wasi = Some(wasi);
|
|
99
|
-
self.stdout_pipe = Some(stdout);
|
|
100
|
-
self.stderr_pipe = Some(stderr);
|
|
78
|
+
/// The state of the Calls open on this instance.
|
|
79
|
+
pub(crate) fn calls_mut(&mut self) -> &mut Calls<wasmtime::Error> {
|
|
80
|
+
&mut self.calls
|
|
101
81
|
}
|
|
102
82
|
|
|
103
83
|
pub(crate) fn bind_on_dispatch(&mut self, handler: Arc<dyn DispatchHandler>) {
|
|
104
|
-
self.
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
pub(crate) fn stdout_bytes(&self) -> Vec<u8> {
|
|
108
|
-
self.stdout_pipe
|
|
109
|
-
.as_ref()
|
|
110
|
-
.map(|p| p.contents().to_vec())
|
|
111
|
-
.unwrap_or_default()
|
|
84
|
+
self.calls.bind(handler);
|
|
112
85
|
}
|
|
113
86
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
.
|
|
87
|
+
/// The trap ending an invocation its host gave up on.
|
|
88
|
+
pub(crate) fn abandonment(&self) -> Option<wasmtime::Error> {
|
|
89
|
+
self.calls
|
|
90
|
+
.abandoned()
|
|
91
|
+
.then(|| wasmtime::Error::msg(ABANDONED))
|
|
119
92
|
}
|
|
120
93
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
pub(crate) fn on_dispatch(&self) -> Option<Arc<dyn DispatchHandler>> {
|
|
124
|
-
self.on_dispatch.clone()
|
|
94
|
+
pub(crate) fn wasi(&self) -> &Wasi {
|
|
95
|
+
&self.wasi
|
|
125
96
|
}
|
|
126
97
|
|
|
127
|
-
pub(crate) fn wasi_mut(&mut self) -> &mut
|
|
128
|
-
self.wasi
|
|
129
|
-
.as_mut()
|
|
130
|
-
.expect("WASI context not initialised — the driver must install it before any WASI use")
|
|
98
|
+
pub(crate) fn wasi_mut(&mut self) -> &mut Wasi {
|
|
99
|
+
&mut self.wasi
|
|
131
100
|
}
|
|
132
101
|
|
|
133
102
|
pub(crate) fn set_deadline(&mut self, deadline: Option<Instant>) {
|
|
@@ -146,13 +115,13 @@ impl Invocation {
|
|
|
146
115
|
/// image's initial allocation and the high-water mark of earlier
|
|
147
116
|
/// invocations do not consume the budget.
|
|
148
117
|
pub(crate) fn arm_memory_cap(&mut self, baseline: usize) {
|
|
149
|
-
self.limiter.activate(baseline);
|
|
118
|
+
self.limiter.0.activate(baseline);
|
|
150
119
|
}
|
|
151
120
|
|
|
152
121
|
/// Disarmed once the export returns, so post-run host bookkeeping such
|
|
153
122
|
/// as reading the answer back is not charged to the script.
|
|
154
123
|
pub(crate) fn disarm_memory_cap(&mut self) {
|
|
155
|
-
self.limiter.deactivate();
|
|
124
|
+
self.limiter.0.deactivate();
|
|
156
125
|
}
|
|
157
126
|
|
|
158
127
|
/// Stamped right before the guest export call, so the bracket matches
|
|
@@ -172,6 +141,6 @@ impl Invocation {
|
|
|
172
141
|
}
|
|
173
142
|
|
|
174
143
|
pub(crate) fn memory_peak(&self) -> usize {
|
|
175
|
-
self.limiter.peak()
|
|
144
|
+
self.limiter.0.peak()
|
|
176
145
|
}
|
|
177
146
|
}
|
|
@@ -13,17 +13,18 @@
|
|
|
13
13
|
mod abi;
|
|
14
14
|
mod ambient;
|
|
15
15
|
mod cache;
|
|
16
|
-
mod capture;
|
|
17
16
|
mod config;
|
|
18
|
-
mod
|
|
17
|
+
mod descriptors;
|
|
19
18
|
mod driver;
|
|
20
19
|
mod exports;
|
|
21
|
-
mod
|
|
20
|
+
mod guest;
|
|
21
|
+
mod imports;
|
|
22
22
|
mod instance_pre;
|
|
23
23
|
mod invocation;
|
|
24
|
-
mod io;
|
|
25
24
|
mod limiter;
|
|
25
|
+
mod preview1;
|
|
26
26
|
mod trap;
|
|
27
|
+
mod wasi;
|
|
27
28
|
|
|
28
29
|
pub use config::Config;
|
|
29
30
|
pub use driver::Driver;
|
|
@@ -1,65 +1,11 @@
|
|
|
1
|
-
//!
|
|
2
|
-
//! linear-memory size captured at invocation entry is charged, so the
|
|
3
|
-
//! image's initial allocation is outside the budget.
|
|
1
|
+
//! Hands wasmtime's growth hook to the runtime's per-invocation memory cap.
|
|
4
2
|
|
|
3
|
+
use kobako_runtime::endpoint::MemoryCap;
|
|
5
4
|
use wasmtime::ResourceLimiter;
|
|
6
5
|
|
|
7
|
-
///
|
|
8
|
-
///
|
|
9
|
-
|
|
10
|
-
/// `max_memory` is the byte cap on per-invocation growth (`None` disables
|
|
11
|
-
/// the cap). `baseline` is the linear-memory size captured at invocation
|
|
12
|
-
/// entry by `MemoryLimiter::activate`; the limiter charges only the
|
|
13
|
-
/// `memory.grow` delta past `baseline` against `max_memory`, so the
|
|
14
|
-
/// mruby image's initial allocation and any high-water mark left by
|
|
15
|
-
/// prior invocations on the same Sandbox do not consume the budget.
|
|
16
|
-
/// `cap_active` gates whether the cap is enforced — wasmtime's
|
|
17
|
-
/// `ResourceLimiter` also fires for the module's declared initial
|
|
18
|
-
/// allocation at instantiation time, but the cap stays dormant until
|
|
19
|
-
/// `MemoryLimiter::activate` flips the flag for one `Driver` invoke
|
|
20
|
-
/// call. When `cap_active` is `false`, the limiter always allows
|
|
21
|
-
/// growth.
|
|
22
|
-
///
|
|
23
|
-
/// When `memory.grow` would push the per-invocation delta past
|
|
24
|
-
/// `max_memory`, the limiter returns `MemoryLimitTrap` from
|
|
25
|
-
/// `memory_growing`; wasmtime turns that into the trap surfaced to the
|
|
26
|
-
/// host as a guest invocation failure.
|
|
27
|
-
#[derive(Debug, Clone, Copy)]
|
|
28
|
-
pub(crate) struct MemoryLimiter {
|
|
29
|
-
max_memory: Option<usize>,
|
|
30
|
-
baseline: usize,
|
|
31
|
-
cap_active: bool,
|
|
32
|
-
peak: usize,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
impl MemoryLimiter {
|
|
36
|
-
pub(crate) fn new(max_memory: Option<usize>) -> Self {
|
|
37
|
-
Self {
|
|
38
|
-
max_memory,
|
|
39
|
-
baseline: 0,
|
|
40
|
-
cap_active: false,
|
|
41
|
-
peak: 0,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/// The cap stays dormant until armed, because the module's declared
|
|
46
|
-
/// initial memory is allocated during instantiation and must pass.
|
|
47
|
-
pub(crate) fn activate(&mut self, baseline: usize) {
|
|
48
|
-
self.baseline = baseline;
|
|
49
|
-
self.cap_active = true;
|
|
50
|
-
self.peak = 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
pub(crate) fn deactivate(&mut self) {
|
|
54
|
-
self.cap_active = false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// A grow the cap rejects never updates the peak, so the reported value
|
|
58
|
-
/// never exceeds `memory_limit`.
|
|
59
|
-
pub(crate) fn peak(&self) -> usize {
|
|
60
|
-
self.peak
|
|
61
|
-
}
|
|
62
|
-
}
|
|
6
|
+
/// wasmtime asks this as `memory.grow` runs; an `Err` becomes the trap
|
|
7
|
+
/// surfaced to the host as a guest invocation failure.
|
|
8
|
+
pub(crate) struct MemoryLimiter(pub(crate) MemoryCap);
|
|
63
9
|
|
|
64
10
|
impl ResourceLimiter for MemoryLimiter {
|
|
65
11
|
fn memory_growing(
|
|
@@ -68,18 +14,7 @@ impl ResourceLimiter for MemoryLimiter {
|
|
|
68
14
|
desired: usize,
|
|
69
15
|
_maximum: Option<usize>,
|
|
70
16
|
) -> wasmtime::Result<bool> {
|
|
71
|
-
|
|
72
|
-
return Ok(true);
|
|
73
|
-
}
|
|
74
|
-
let delta = desired.saturating_sub(self.baseline);
|
|
75
|
-
if let Some(limit) = self.max_memory {
|
|
76
|
-
if delta > limit {
|
|
77
|
-
return Err(wasmtime::Error::new(MemoryLimitTrap { desired, limit }));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if delta > self.peak {
|
|
81
|
-
self.peak = delta;
|
|
82
|
-
}
|
|
17
|
+
self.0.charge(desired).map_err(wasmtime::Error::new)?;
|
|
83
18
|
Ok(true)
|
|
84
19
|
}
|
|
85
20
|
|
|
@@ -92,162 +27,3 @@ impl ResourceLimiter for MemoryLimiter {
|
|
|
92
27
|
Ok(true)
|
|
93
28
|
}
|
|
94
29
|
}
|
|
95
|
-
|
|
96
|
-
/// Marker error returned from `MemoryLimiter::memory_growing` when the
|
|
97
|
-
/// per-invocation memory cap is exceeded. Downcast from the wasmtime
|
|
98
|
-
/// trap error to classify the failure as a memory-limit `Trap`. Callers
|
|
99
|
-
/// use the `Display` impl below — no field is read directly — so the
|
|
100
|
-
/// inner state stays private.
|
|
101
|
-
#[derive(Debug)]
|
|
102
|
-
pub(crate) struct MemoryLimitTrap {
|
|
103
|
-
desired: usize,
|
|
104
|
-
limit: usize,
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
impl MemoryLimitTrap {
|
|
108
|
-
#[cfg(test)]
|
|
109
|
-
pub(crate) fn new(desired: usize, limit: usize) -> Self {
|
|
110
|
-
Self { desired, limit }
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
impl std::fmt::Display for MemoryLimitTrap {
|
|
115
|
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
116
|
-
write!(
|
|
117
|
-
f,
|
|
118
|
-
"memory usage exceeded memory_limit: \
|
|
119
|
-
requested={} bytes, limit={} bytes",
|
|
120
|
-
self.desired, self.limit
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
impl std::error::Error for MemoryLimitTrap {}
|
|
126
|
-
|
|
127
|
-
#[cfg(test)]
|
|
128
|
-
mod tests {
|
|
129
|
-
//! Unit tests for `MemoryLimiter` — the per-invocation memory
|
|
130
|
-
//! delta cap. The Ruby-facing E2E suite exercises the full path
|
|
131
|
-
//! through wasmtime; these tests pin the pure delta arithmetic so
|
|
132
|
-
//! a regression that breaks the baseline accounting (e.g. dropping
|
|
133
|
-
//! the `baseline` subtraction, or letting `activate` carry stale
|
|
134
|
-
//! state across invocations) is caught without spinning up a
|
|
135
|
-
//! Store.
|
|
136
|
-
use super::{MemoryLimitTrap, MemoryLimiter};
|
|
137
|
-
use wasmtime::ResourceLimiter;
|
|
138
|
-
|
|
139
|
-
fn assert_growing(limiter: &mut MemoryLimiter, desired: usize) {
|
|
140
|
-
assert!(
|
|
141
|
-
limiter.memory_growing(0, desired, None).unwrap(),
|
|
142
|
-
"expected memory_growing({desired}) to allow growth"
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
fn assert_trapping(limiter: &mut MemoryLimiter, desired: usize) {
|
|
147
|
-
let err = limiter
|
|
148
|
-
.memory_growing(0, desired, None)
|
|
149
|
-
.expect_err("expected memory_growing to trap");
|
|
150
|
-
assert!(
|
|
151
|
-
err.downcast_ref::<MemoryLimitTrap>().is_some(),
|
|
152
|
-
"expected MemoryLimitTrap, got {err:?}"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// @behavior S-113
|
|
157
|
-
#[test]
|
|
158
|
-
fn dormant_limiter_allows_any_growth() {
|
|
159
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
160
|
-
// Without `activate`, the cap is dormant — the module's
|
|
161
|
-
// declared initial allocation must pass through unconditionally.
|
|
162
|
-
assert_growing(&mut limiter, 100 << 20);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// @behavior S-114
|
|
166
|
-
#[test]
|
|
167
|
-
fn delta_below_cap_passes_after_activate() {
|
|
168
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
169
|
-
limiter.activate(2 << 20);
|
|
170
|
-
// baseline=2 MiB, desired=2.5 MiB → delta=0.5 MiB ≤ 1 MiB cap.
|
|
171
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// @behavior S-121
|
|
175
|
-
#[test]
|
|
176
|
-
fn delta_past_cap_traps_with_memory_limit_trap() {
|
|
177
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
178
|
-
limiter.activate(2 << 20);
|
|
179
|
-
// baseline=2 MiB, desired=4 MiB → delta=2 MiB > 1 MiB cap.
|
|
180
|
-
assert_trapping(&mut limiter, 4 << 20);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// @behavior S-008
|
|
184
|
-
#[test]
|
|
185
|
-
fn activate_resets_baseline_on_each_invocation() {
|
|
186
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
187
|
-
limiter.activate(2 << 20);
|
|
188
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 20));
|
|
189
|
-
// Second invocation: linear memory has grown to 3 MiB. Re-arming
|
|
190
|
-
// must re-anchor the baseline so the next 1 MiB of growth fits
|
|
191
|
-
// the per-invocation budget rather than being charged against
|
|
192
|
-
// the prior invocation's residue.
|
|
193
|
-
limiter.activate(3 << 20);
|
|
194
|
-
assert_growing(&mut limiter, (3 << 20) + (1 << 20));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// @behavior S-009
|
|
198
|
-
#[test]
|
|
199
|
-
fn disabled_cap_ignores_delta_size() {
|
|
200
|
-
let mut limiter = MemoryLimiter::new(None);
|
|
201
|
-
limiter.activate(0);
|
|
202
|
-
assert_growing(&mut limiter, 100 << 20);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// @behavior S-116
|
|
206
|
-
#[test]
|
|
207
|
-
fn peak_starts_at_zero_before_any_grow() {
|
|
208
|
-
let limiter = MemoryLimiter::new(Some(1 << 20));
|
|
209
|
-
assert_eq!(limiter.peak(), 0);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// @behavior S-115
|
|
213
|
-
#[test]
|
|
214
|
-
fn peak_tracks_high_water_of_delta_past_baseline() {
|
|
215
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
216
|
-
limiter.activate(2 << 20);
|
|
217
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 18)); // delta=256 KiB
|
|
218
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19)); // delta=512 KiB (new peak)
|
|
219
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 17)); // delta=128 KiB (below peak)
|
|
220
|
-
assert_eq!(limiter.peak(), 1 << 19);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// @behavior S-062
|
|
224
|
-
#[test]
|
|
225
|
-
fn trap_does_not_update_peak() {
|
|
226
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
227
|
-
limiter.activate(2 << 20);
|
|
228
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19)); // delta=512 KiB
|
|
229
|
-
assert_trapping(&mut limiter, (2 << 20) + (2 << 20)); // would be 2 MiB > 1 MiB cap
|
|
230
|
-
// Peak reflects the last accepted grow, not the rejected desired.
|
|
231
|
-
assert_eq!(limiter.peak(), 1 << 19);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// @behavior S-117
|
|
235
|
-
#[test]
|
|
236
|
-
fn activate_resets_peak_for_new_invocation() {
|
|
237
|
-
let mut limiter = MemoryLimiter::new(Some(1 << 20));
|
|
238
|
-
limiter.activate(2 << 20);
|
|
239
|
-
assert_growing(&mut limiter, (2 << 20) + (1 << 19));
|
|
240
|
-
assert_eq!(limiter.peak(), 1 << 19);
|
|
241
|
-
limiter.activate(3 << 20);
|
|
242
|
-
assert_eq!(limiter.peak(), 0);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// @behavior S-118
|
|
246
|
-
#[test]
|
|
247
|
-
fn disabled_cap_still_tracks_peak() {
|
|
248
|
-
let mut limiter = MemoryLimiter::new(None);
|
|
249
|
-
limiter.activate(1 << 20);
|
|
250
|
-
assert_growing(&mut limiter, (1 << 20) + (4 << 20));
|
|
251
|
-
assert_eq!(limiter.peak(), 4 << 20);
|
|
252
|
-
}
|
|
253
|
-
}
|