kobako 0.9.1 → 0.10.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 +36 -0
- data/Cargo.lock +3 -1
- data/Cargo.toml +6 -0
- data/README.md +35 -14
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +11 -1
- data/ext/kobako/src/runtime/ambient.rs +78 -0
- data/ext/kobako/src/runtime/cache.rs +168 -4
- data/ext/kobako/src/runtime/dispatch.rs +2 -2
- data/ext/kobako/src/runtime/exports.rs +32 -21
- data/ext/kobako/src/runtime/instance_pre.rs +97 -0
- data/ext/kobako/src/runtime/invocation.rs +13 -70
- data/ext/kobako/src/runtime.rs +373 -369
- data/lib/kobako/catalog/handles.rb +26 -7
- data/lib/kobako/catalog/namespaces.rb +22 -4
- data/lib/kobako/catalog/snippets.rb +9 -1
- data/lib/kobako/codec/decoder.rb +5 -1
- data/lib/kobako/codec.rb +5 -6
- data/lib/kobako/errors.rb +11 -1
- data/lib/kobako/namespace.rb +16 -2
- data/lib/kobako/pool.rb +182 -0
- data/lib/kobako/sandbox.rb +16 -14
- data/lib/kobako/transport/dispatcher.rb +29 -10
- data/lib/kobako/transport/request.rb +1 -9
- data/lib/kobako/transport/response.rb +9 -2
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/release-please-config.json +17 -3
- data/sig/kobako/catalog/handles.rbs +4 -0
- data/sig/kobako/errors.rbs +3 -0
- data/sig/kobako/namespace.rbs +2 -0
- data/sig/kobako/pool.rbs +44 -0
- data/sig/kobako/sandbox.rbs +2 -2
- data/sig/kobako/transport/dispatcher.rbs +4 -0
- data/sig/kobako/transport/request.rbs +0 -3
- data/sig/kobako/transport/response.rbs +3 -0
- metadata +5 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//! Per-path cache of pre-instantiated wasmtime artifacts.
|
|
2
|
+
//!
|
|
3
|
+
//! The `Linker` wiring (the WASI preview1 import set plus the
|
|
4
|
+
//! `__kobako_dispatch` host import) and its type-check against the
|
|
5
|
+
//! compiled Module are identical for every `Kobako::Runtime` on the
|
|
6
|
+
//! same Guest Binary — both host closures read all their state from
|
|
7
|
+
//! the `Invocation` inside the calling Store, never from the Runtime.
|
|
8
|
+
//! Caching the resolved `InstancePre` per path leaves only the
|
|
9
|
+
//! `instantiate` call itself on the `Runtime.from_path` hot path.
|
|
10
|
+
//!
|
|
11
|
+
//! Concurrency: see `super::cache` — under Ruby's GVL the Mutex serves
|
|
12
|
+
//! `Sync` bounds rather than real contention.
|
|
13
|
+
|
|
14
|
+
use std::collections::HashMap;
|
|
15
|
+
use std::path::{Path, PathBuf};
|
|
16
|
+
use std::sync::{Mutex, OnceLock};
|
|
17
|
+
|
|
18
|
+
use magnus::{Error as MagnusError, Ruby};
|
|
19
|
+
use wasmtime::{Caller, InstancePre, Linker};
|
|
20
|
+
use wasmtime_wasi::p1;
|
|
21
|
+
|
|
22
|
+
use super::cache::{cached_module, shared_engine};
|
|
23
|
+
use super::invocation::Invocation;
|
|
24
|
+
use super::{dispatch, setup_err, trap};
|
|
25
|
+
|
|
26
|
+
static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocation>>>> =
|
|
27
|
+
OnceLock::new();
|
|
28
|
+
|
|
29
|
+
/// Look up `path` in the per-path `InstancePre` cache, wiring the
|
|
30
|
+
/// Linker and resolving the Module's imports on a miss. Compilation
|
|
31
|
+
/// faults surface through `cached_module`; import-resolution faults
|
|
32
|
+
/// raise `Kobako::SetupError` (docs/behavior.md E-41).
|
|
33
|
+
pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>, MagnusError> {
|
|
34
|
+
let cache = INSTANCE_PRE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
|
35
|
+
|
|
36
|
+
if let Some(pre) = cache
|
|
37
|
+
.lock()
|
|
38
|
+
.expect("instance_pre cache mutex poisoned")
|
|
39
|
+
.get(path)
|
|
40
|
+
.cloned()
|
|
41
|
+
{
|
|
42
|
+
return Ok(pre);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let module = cached_module(path)?;
|
|
46
|
+
let linker = build_linker()?;
|
|
47
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
48
|
+
let pre = linker
|
|
49
|
+
.instantiate_pre(&module)
|
|
50
|
+
.map_err(|e| trap::instantiate_err(&ruby, e))?;
|
|
51
|
+
cache
|
|
52
|
+
.lock()
|
|
53
|
+
.expect("instance_pre cache mutex poisoned")
|
|
54
|
+
.insert(path.to_path_buf(), pre.clone());
|
|
55
|
+
Ok(pre)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Build the host-import `Linker` every Guest Binary instantiates
|
|
59
|
+
/// against.
|
|
60
|
+
fn build_linker() -> Result<Linker<Invocation>, MagnusError> {
|
|
61
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
62
|
+
let mut linker: Linker<Invocation> = Linker::new(shared_engine()?);
|
|
63
|
+
|
|
64
|
+
// Wire the wasmtime-wasi preview1 WASI imports. Routes guest fd 1/2
|
|
65
|
+
// to the MemoryOutputPipes set up before each run via
|
|
66
|
+
// `Runtime::eval`. The closure pulls a `&mut WasiP1Ctx` out of
|
|
67
|
+
// Invocation; the panic semantics live inside `Invocation::wasi_mut`
|
|
68
|
+
// so the wiring stays honest about its precondition.
|
|
69
|
+
p1::add_to_linker_sync(&mut linker, |state: &mut Invocation| state.wasi_mut())
|
|
70
|
+
.map_err(|e| setup_err(&ruby, format!("failed to set up the WASI runtime: {}", e)))?;
|
|
71
|
+
|
|
72
|
+
// `__kobako_dispatch` host import. Signature per docs/wire-codec.md
|
|
73
|
+
// § ABI Signatures:
|
|
74
|
+
// (req_ptr: i32, req_len: i32) -> i64
|
|
75
|
+
// Decodes the Request bytes, dispatches via the Ruby-side
|
|
76
|
+
// dispatch Proc (bound per-Sandbox through `Runtime#on_dispatch=`),
|
|
77
|
+
// allocates a guest buffer through `__kobako_alloc`, writes
|
|
78
|
+
// the Response bytes there, and returns the packed
|
|
79
|
+
// `(ptr<<32)|len`. The dispatcher returns 0 on any wire-layer
|
|
80
|
+
// fault (including no Proc bound); see `dispatch::handle`.
|
|
81
|
+
linker
|
|
82
|
+
.func_wrap(
|
|
83
|
+
"env",
|
|
84
|
+
"__kobako_dispatch",
|
|
85
|
+
|mut caller: Caller<'_, Invocation>, req_ptr: i32, req_len: i32| -> i64 {
|
|
86
|
+
dispatch::handle(&mut caller, req_ptr, req_len)
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
.map_err(|e| {
|
|
90
|
+
setup_err(
|
|
91
|
+
&ruby,
|
|
92
|
+
format!("failed to set up the host callback bridge: {}", e),
|
|
93
|
+
)
|
|
94
|
+
})?;
|
|
95
|
+
|
|
96
|
+
Ok(linker)
|
|
97
|
+
}
|
|
@@ -2,29 +2,28 @@
|
|
|
2
2
|
//! [SPEC.md Single-Invocation Slot] (one `Invocation` per OS thread
|
|
3
3
|
//! for the lifetime of one `Runtime::eval` / `Runtime::run` call).
|
|
4
4
|
//!
|
|
5
|
-
//! Owned
|
|
6
|
-
//! and threaded through every host import —
|
|
7
|
-
//! dispatcher reads the bound dispatch Proc,
|
|
8
|
-
//! on `crate::runtime::Runtime` install
|
|
9
|
-
//!
|
|
5
|
+
//! Owned as the data of each per-invocation `wasmtime::Store`
|
|
6
|
+
//! (docs/behavior.md B-49) and threaded through every host import —
|
|
7
|
+
//! the `__kobako_dispatch` dispatcher reads the bound dispatch Proc,
|
|
8
|
+
//! while the run-path methods on `crate::runtime::Runtime` install the
|
|
9
|
+
//! invocation's WASI context + pipes at Store creation
|
|
10
|
+
//! (docs/behavior.md B-03 / B-04).
|
|
10
11
|
//!
|
|
11
12
|
//! The slot also carries the per-invocation wall-clock deadline
|
|
12
13
|
//! (docs/behavior.md B-01, E-19) and the per-invocation linear-memory
|
|
13
14
|
//! delta cap `MemoryLimiter` (docs/behavior.md B-01, E-20). Both are
|
|
14
15
|
//! read from the wasmtime `epoch_deadline_callback` / `ResourceLimiter`
|
|
15
|
-
//! callbacks installed in `crate::runtime::Runtime::
|
|
16
|
+
//! callbacks installed in `crate::runtime::Runtime::new_store`. The
|
|
16
17
|
//! memory cap measures only the `memory.grow` delta past the linear-
|
|
17
|
-
//! memory size captured at invocation entry — the
|
|
18
|
-
//!
|
|
19
|
-
//! budget.
|
|
18
|
+
//! memory size captured at invocation entry — the image's initial
|
|
19
|
+
//! allocation is outside the budget.
|
|
20
20
|
//!
|
|
21
|
-
//! [SPEC.md Single-Invocation Slot]:
|
|
21
|
+
//! [SPEC.md Single-Invocation Slot]: ../../../../SPEC.md
|
|
22
22
|
|
|
23
|
-
use std::cell::{Ref, RefCell, RefMut};
|
|
24
23
|
use std::time::{Duration, Instant};
|
|
25
24
|
|
|
26
25
|
use magnus::{value::Opaque, Value};
|
|
27
|
-
use wasmtime::
|
|
26
|
+
use wasmtime::ResourceLimiter;
|
|
28
27
|
use wasmtime_wasi::p1::WasiP1Ctx;
|
|
29
28
|
use wasmtime_wasi::p2::pipe::MemoryOutputPipe;
|
|
30
29
|
|
|
@@ -134,14 +133,14 @@ impl Invocation {
|
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
/// Return the current per-run deadline. Read from the epoch-deadline
|
|
137
|
-
/// callback installed by `crate::runtime::Runtime::
|
|
136
|
+
/// callback installed by `crate::runtime::Runtime::new_store`.
|
|
138
137
|
pub(super) fn deadline(&self) -> Option<Instant> {
|
|
139
138
|
self.deadline
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
/// Mutable handle to the embedded `MemoryLimiter`. Required by
|
|
143
142
|
/// the wasmtime `ResourceLimiter` callback wiring in
|
|
144
|
-
/// `crate::runtime::Runtime::
|
|
143
|
+
/// `crate::runtime::Runtime::new_store`
|
|
145
144
|
/// (`store.limiter(|state| state.limiter_mut())`); kept private to
|
|
146
145
|
/// the wasm submodule so the only public surface for arming the
|
|
147
146
|
/// cap goes through `Invocation::arm_memory_cap` /
|
|
@@ -362,62 +361,6 @@ impl std::fmt::Display for TimeoutTrap {
|
|
|
362
361
|
|
|
363
362
|
impl std::error::Error for TimeoutTrap {}
|
|
364
363
|
|
|
365
|
-
/// Interior-mutability wrapper around `wasmtime::Store<Invocation>`.
|
|
366
|
-
///
|
|
367
|
-
/// Magnus requires `Send + Sync` for wrapped types. `wasmtime::Store` is not
|
|
368
|
-
/// `Sync`, so we wrap it in a `RefCell`. `RefCell` alone is sufficient
|
|
369
|
-
/// because magnus enforces single-threaded GVL access from Ruby; `Send` and
|
|
370
|
-
/// `Sync` are asserted via the unsafe impls below.
|
|
371
|
-
pub(super) struct StoreCell {
|
|
372
|
-
inner: RefCell<WtStore<Invocation>>,
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
impl StoreCell {
|
|
376
|
-
/// Wrap a freshly-built `wasmtime::Store<Invocation>` so it can be owned
|
|
377
|
-
/// by the magnus-wrapped `Runtime`.
|
|
378
|
-
pub(super) fn new(store: WtStore<Invocation>) -> Self {
|
|
379
|
-
Self {
|
|
380
|
-
inner: RefCell::new(store),
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/// Immutable borrow of the wrapped Store. Panics if a `borrow_mut()`
|
|
385
|
-
/// is currently live — matches `RefCell::borrow` semantics.
|
|
386
|
-
pub(super) fn borrow(&self) -> Ref<'_, WtStore<Invocation>> {
|
|
387
|
-
self.inner.borrow()
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/// Mutable borrow of the wrapped Store. Panics if any other borrow is
|
|
391
|
-
/// currently live — matches `RefCell::borrow_mut` semantics.
|
|
392
|
-
pub(super) fn borrow_mut(&self) -> RefMut<'_, WtStore<Invocation>> {
|
|
393
|
-
self.inner.borrow_mut()
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// SAFETY: magnus requires `Send + Sync` on `#[magnus::wrap]` types. Both
|
|
398
|
-
// claims hold under the GVL invariant:
|
|
399
|
-
//
|
|
400
|
-
// * Send — `wasmtime::Store<Invocation>` is itself `Send` (verified
|
|
401
|
-
// upstream by wasmtime; see `wasmtime::Store`'s trait impls).
|
|
402
|
-
// `RefCell<T>: Send` whenever `T: Send`. The remaining stored state
|
|
403
|
-
// (`Invocation`) holds `Opaque<Value>` for the Ruby Server handle —
|
|
404
|
-
// `Opaque<Value>` is documented as `Send` by magnus precisely so
|
|
405
|
-
// wrapped objects can satisfy this bound.
|
|
406
|
-
//
|
|
407
|
-
// * Sync — `RefCell` is *not* `Sync` in the general Rust sense
|
|
408
|
-
// (concurrent `borrow_mut` is UB). We assert `Sync` here because the
|
|
409
|
-
// GVL serialises every call into Ruby C and every entry into magnus-
|
|
410
|
-
// wrapped methods onto a single OS thread at a time: by the time the
|
|
411
|
-
// `Sync` bound matters, magnus has already established that only one
|
|
412
|
-
// thread can be inside the wrapper. Cross-thread mutation cannot
|
|
413
|
-
// occur. If a future magnus release adopts a thread model that
|
|
414
|
-
// permits concurrent access to wrapped objects, this assertion would
|
|
415
|
-
// have to revert and `StoreCell` would need to switch to
|
|
416
|
-
// `Mutex<wasmtime::Store<…>>` — but as of magnus 0.8 the contract
|
|
417
|
-
// holds.
|
|
418
|
-
unsafe impl Send for StoreCell {}
|
|
419
|
-
unsafe impl Sync for StoreCell {}
|
|
420
|
-
|
|
421
364
|
#[cfg(test)]
|
|
422
365
|
mod tests {
|
|
423
366
|
//! Unit tests for `MemoryLimiter` — the per-invocation memory
|