kobako 0.26.0 → 0.28.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 +103 -0
- data/Cargo.lock +117 -140
- data/README.md +156 -118
- data/ROADMAP.md +23 -22
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +38 -0
- data/crates/kobako-runtime/Cargo.toml +2 -2
- data/crates/kobako-runtime/README.md +10 -12
- data/crates/kobako-runtime/src/error.rs +8 -9
- data/crates/kobako-runtime/src/lib.rs +1 -1
- data/crates/kobako-runtime/src/profile.rs +1 -1
- data/crates/kobako-runtime/src/runtime.rs +31 -17
- data/crates/kobako-runtime/src/snapshot.rs +13 -10
- data/crates/kobako-runtime/src/yielder.rs +9 -6
- data/crates/kobako-transport/CHANGELOG.md +35 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +43 -27
- data/crates/kobako-transport/src/abi.rs +11 -14
- data/crates/kobako-transport/src/envelope/bytes.rs +18 -6
- data/crates/kobako-transport/src/envelope/compile_reply.rs +139 -0
- data/crates/kobako-transport/src/envelope/outcome.rs +17 -16
- data/crates/kobako-transport/src/envelope/preamble.rs +513 -0
- data/crates/kobako-transport/src/envelope/reply.rs +77 -53
- data/crates/kobako-transport/src/envelope/request.rs +295 -0
- data/crates/kobako-transport/src/envelope/run.rs +1 -2
- data/crates/kobako-transport/src/envelope/tag.rs +49 -0
- data/crates/kobako-transport/src/envelope/unsupported.rs +105 -0
- data/crates/kobako-transport/src/envelope.rs +19 -12
- data/crates/kobako-transport/src/lib.rs +0 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +51 -0
- data/crates/kobako-wasmtime/Cargo.toml +7 -5
- data/crates/kobako-wasmtime/README.md +8 -10
- data/crates/kobako-wasmtime/src/abi.rs +5 -8
- data/crates/kobako-wasmtime/src/ambient.rs +2 -2
- data/crates/kobako-wasmtime/src/cache.rs +25 -59
- data/crates/kobako-wasmtime/src/capture.rs +4 -11
- data/crates/kobako-wasmtime/src/config.rs +11 -14
- data/crates/kobako-wasmtime/src/dispatch.rs +70 -80
- data/crates/kobako-wasmtime/src/driver.rs +133 -86
- data/crates/kobako-wasmtime/src/exports.rs +88 -34
- data/crates/kobako-wasmtime/src/guest_mem.rs +145 -138
- data/crates/kobako-wasmtime/src/instance_pre.rs +11 -22
- data/crates/kobako-wasmtime/src/invocation.rs +55 -372
- data/crates/kobako-wasmtime/src/io.rs +286 -0
- data/crates/kobako-wasmtime/src/lib.rs +3 -24
- data/crates/kobako-wasmtime/src/limiter.rs +253 -0
- data/crates/kobako-wasmtime/src/trap.rs +32 -45
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +69 -0
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +94 -0
- data/crates/kobako-wasmtime/tests/support/mod.rs +34 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +50 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +2 -2
- data/ext/kobako/src/runtime/bridge.rs +38 -47
- data/ext/kobako/src/runtime/errors.rs +26 -32
- data/ext/kobako/src/runtime/gvl.rs +4 -12
- data/ext/kobako/src/runtime.rs +169 -212
- data/lib/kobako/capture.rb +1 -4
- data/lib/kobako/catalog/extensions.rb +21 -59
- data/lib/kobako/catalog/flags.rb +41 -0
- data/lib/kobako/catalog/handles.rb +7 -54
- data/lib/kobako/catalog/services.rb +7 -61
- data/lib/kobako/catalog/snippets.rb +42 -66
- data/lib/kobako/catalog.rb +2 -10
- data/lib/kobako/codec/decoder.rb +9 -28
- data/lib/kobako/codec/encoder.rb +3 -22
- data/lib/kobako/codec/error.rb +6 -27
- data/lib/kobako/codec/ext_types.rb +72 -94
- data/lib/kobako/codec/handle_walk.rb +65 -130
- data/lib/kobako/codec/nesting.rb +3 -8
- data/lib/kobako/codec/state.rb +4 -10
- data/lib/kobako/codec/utils.rb +6 -34
- data/lib/kobako/codec.rb +3 -22
- data/lib/kobako/constant_name.rb +8 -0
- data/lib/kobako/context.rb +51 -112
- data/lib/kobako/errors.rb +51 -127
- data/lib/kobako/execution.rb +16 -21
- data/lib/kobako/extension.rb +23 -40
- data/lib/kobako/handle.rb +7 -12
- data/lib/kobako/outcome.rb +51 -80
- data/lib/kobako/payload/arguments.rb +1 -16
- data/lib/kobako/payload.rb +4 -12
- data/lib/kobako/pool.rb +23 -53
- data/lib/kobako/runtime.rb +7 -21
- data/lib/kobako/sandbox.rb +96 -133
- data/lib/kobako/sandbox_options.rb +38 -84
- data/lib/kobako/snippet/binary.rb +1 -16
- data/lib/kobako/snippet/source.rb +1 -16
- data/lib/kobako/snippet.rb +1 -9
- data/lib/kobako/transport/call.rb +1 -14
- data/lib/kobako/transport/dispatcher.rb +112 -195
- data/lib/kobako/transport/error.rb +6 -20
- data/lib/kobako/transport/exposure.rb +5 -10
- data/lib/kobako/transport/reflection.rb +39 -50
- data/lib/kobako/transport/run.rb +17 -71
- data/lib/kobako/transport/yielder.rb +21 -73
- data/lib/kobako/transport.rb +3 -15
- data/lib/kobako/unresolved.rb +4 -11
- data/lib/kobako/usage.rb +2 -6
- data/lib/kobako/version.rb +1 -1
- data/sig/kobako/catalog/extensions.rbs +2 -2
- data/sig/kobako/catalog/flags.rbs +17 -0
- data/sig/kobako/catalog/services.rbs +0 -6
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/codec/ext_types.rbs +10 -8
- data/sig/kobako/codec/handle_walk.rbs +9 -7
- data/sig/kobako/codec/utils.rbs +2 -2
- data/sig/kobako/codec.rbs +2 -2
- data/sig/kobako/constant_name.rbs +3 -0
- data/sig/kobako/context.rbs +2 -3
- data/sig/kobako/errors.rbs +6 -0
- data/sig/kobako/outcome.rbs +6 -8
- data/sig/kobako/runtime.rbs +12 -8
- data/sig/kobako/sandbox.rbs +4 -0
- data/sig/kobako/sandbox_options.rbs +2 -6
- data/sig/kobako/transport/dispatcher.rbs +14 -18
- data/sig/kobako/transport/reflection.rbs +5 -3
- data/sig/kobako/transport/run.rbs +1 -3
- data/sig/kobako/transport/yielder.rbs +3 -3
- data/sig/kobako/transport.rbs +7 -6
- metadata +16 -4
- data/crates/kobako-transport/src/envelope/invocation_frames.rs +0 -241
- data/crates/kobako-wasmtime/src/frames.rs +0 -273
- data/rust-toolchain.toml +0 -11
|
@@ -22,8 +22,6 @@ thread_local! {
|
|
|
22
22
|
static GVL_RELEASED: Cell<bool> = const { Cell::new(false) };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/// Run `f` (one invocation's guest-execution span) and return its value,
|
|
26
|
-
/// releasing the GVL for the span iff `release`. When holding, `f` runs inline.
|
|
27
25
|
pub(crate) fn region<F, R>(release: bool, f: F) -> R
|
|
28
26
|
where
|
|
29
27
|
F: FnOnce() -> R,
|
|
@@ -37,10 +35,8 @@ where
|
|
|
37
35
|
})
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
///
|
|
41
|
-
///
|
|
42
|
-
/// already held — hold mode, or a nested dispatch whose outer frame re-acquired
|
|
43
|
-
/// it.
|
|
38
|
+
/// Runs inline when the GVL is already held — hold mode, or a nested
|
|
39
|
+
/// dispatch whose outer frame re-acquired it.
|
|
44
40
|
pub(crate) fn reenter<F, R>(f: F) -> R
|
|
45
41
|
where
|
|
46
42
|
F: FnOnce() -> R,
|
|
@@ -79,9 +75,8 @@ struct GvlCtx<F, R> {
|
|
|
79
75
|
result: Option<std::thread::Result<R>>,
|
|
80
76
|
}
|
|
81
77
|
|
|
82
|
-
///
|
|
83
|
-
///
|
|
84
|
-
/// unwinding across the C frame, and stashes the outcome for `call_via`.
|
|
78
|
+
/// Runs the closure under `catch_unwind`, so a panic is captured here rather
|
|
79
|
+
/// than unwinding across the C frame.
|
|
85
80
|
unsafe extern "C" fn trampoline<F, R>(data: *mut c_void) -> *mut c_void
|
|
86
81
|
where
|
|
87
82
|
F: FnOnce() -> R,
|
|
@@ -92,9 +87,6 @@ where
|
|
|
92
87
|
ptr::null_mut()
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
/// Drive `f` through one of the `rb_thread_call_*` entry points (`invoke`),
|
|
96
|
-
/// erasing it to the C callback shape and re-raising on the caller's thread any
|
|
97
|
-
/// panic the trampoline caught.
|
|
98
90
|
fn call_via<F, R>(
|
|
99
91
|
f: F,
|
|
100
92
|
invoke: impl FnOnce(unsafe extern "C" fn(*mut c_void) -> *mut c_void, *mut c_void) -> *mut c_void,
|
data/ext/kobako/src/runtime.rs
CHANGED
|
@@ -15,13 +15,7 @@
|
|
|
15
15
|
//! `kobako-wasmtime` crate behind the `kobako_runtime` contract; no wasm
|
|
16
16
|
//! engine type reaches this crate or the Host App.
|
|
17
17
|
//!
|
|
18
|
-
//!
|
|
19
|
-
//!
|
|
20
|
-
//! * `bridge` — the magnus dispatch bridge: `RubyDispatchHandler` plus the
|
|
21
|
-
//! frame-scoped `GuestYielder` Ruby class.
|
|
22
|
-
//! * `errors` — the single boundary mapping the neutral `Trap` /
|
|
23
|
-
//! `SetupError` channels onto the `Kobako::*` classes for a failure that
|
|
24
|
-
//! never produced a `Snapshot` (a could-not-start fault).
|
|
18
|
+
//! Each module holds one responsibility and opens with its own doc.
|
|
25
19
|
|
|
26
20
|
mod bridge;
|
|
27
21
|
mod errors;
|
|
@@ -29,7 +23,7 @@ mod gvl;
|
|
|
29
23
|
|
|
30
24
|
use magnus::{
|
|
31
25
|
function, method, prelude::*, typed_data::DataTypeFunctions, value::Opaque,
|
|
32
|
-
Error as MagnusError, RArray, RModule, RString, Ruby, Symbol, TypedData, Value,
|
|
26
|
+
Error as MagnusError, Exception, RArray, RModule, RString, Ruby, Symbol, TypedData, Value,
|
|
33
27
|
};
|
|
34
28
|
|
|
35
29
|
use std::path::Path;
|
|
@@ -37,11 +31,10 @@ use std::sync::Arc;
|
|
|
37
31
|
use std::time::Duration;
|
|
38
32
|
|
|
39
33
|
use kobako_runtime::dispatch::DispatchHandler;
|
|
40
|
-
use kobako_runtime::error::Trap;
|
|
41
34
|
use kobako_runtime::profile::Profile;
|
|
42
|
-
use kobako_runtime::runtime::{Entry,
|
|
43
|
-
use kobako_runtime::snapshot::{
|
|
44
|
-
use kobako_transport::envelope::{Bindings, Outcome,
|
|
35
|
+
use kobako_runtime::runtime::{Entry, Runtime as ContractRuntime};
|
|
36
|
+
use kobako_runtime::snapshot::{Completion, Snapshot as RuntimeSnapshot};
|
|
37
|
+
use kobako_transport::envelope::{Bindings, Flag, Outcome, Preamble, Snippet, Snippets};
|
|
45
38
|
use kobako_wasmtime::{Config, Driver};
|
|
46
39
|
|
|
47
40
|
/// A Panic's fields as they cross to Ruby: origin, class, message,
|
|
@@ -50,40 +43,24 @@ use kobako_wasmtime::{Config, Driver};
|
|
|
50
43
|
/// shape, matching the `Kobako::Outcome::panic_fields` alias.
|
|
51
44
|
type PanicFields = (String, String, String, Vec<String>, Vec<String>);
|
|
52
45
|
|
|
53
|
-
///
|
|
54
|
-
///
|
|
55
|
-
///
|
|
56
|
-
/// does not outlive this call, so no Ruby allocation can move the
|
|
57
|
-
/// underlying RString between the borrow and the copy — the safety
|
|
58
|
-
/// invariant the inline form relied on is established once here.
|
|
46
|
+
/// The one place the boundary borrows an RString's bytes: the borrow does
|
|
47
|
+
/// not outlive this call, so no Ruby allocation can move the RString
|
|
48
|
+
/// between the borrow and the copy.
|
|
59
49
|
fn rstring_to_vec(s: RString) -> Vec<u8> {
|
|
60
50
|
// SAFETY: see item doc.
|
|
61
51
|
unsafe { s.as_slice() }.to_vec()
|
|
62
52
|
}
|
|
63
53
|
|
|
64
|
-
///
|
|
65
|
-
///
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Ok(Bindings {
|
|
69
|
-
paths: paths.to_vec()?,
|
|
70
|
-
}
|
|
71
|
-
.encode())
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/// Frame the Frame 3 snippet table from the registry's entries — one
|
|
75
|
-
/// `[kind, name, body]` triple each, `kind` a Symbol naming the form so
|
|
76
|
-
/// the wire's discriminant byte stays here. An off-ladder kind raises
|
|
77
|
-
/// rather than defaulting, the same fail-closed posture `from_path`
|
|
78
|
-
/// takes on its Symbol options.
|
|
79
|
-
fn frame_snippets(ruby: &Ruby, entries: RArray) -> Result<Vec<u8>, MagnusError> {
|
|
80
|
-
let mut frame = Snippets {
|
|
54
|
+
/// `kind` arrives as a Symbol so the registry never names the wire's
|
|
55
|
+
/// discriminant; an off-ladder kind raises rather than defaulting.
|
|
56
|
+
fn snippets_from(ruby: &Ruby, entries: RArray) -> Result<Snippets, MagnusError> {
|
|
57
|
+
let mut snippets = Snippets {
|
|
81
58
|
entries: Vec::with_capacity(entries.len()),
|
|
82
59
|
};
|
|
83
60
|
for index in 0..entries.len() as isize {
|
|
84
61
|
let entry: RArray = entries.entry(index)?;
|
|
85
62
|
let kind: Symbol = entry.entry(0)?;
|
|
86
|
-
|
|
63
|
+
snippets.entries.push(match kind.name()?.as_ref() {
|
|
87
64
|
"source" => Snippet::Source {
|
|
88
65
|
name: entry.entry(1)?,
|
|
89
66
|
body: entry.entry(2)?,
|
|
@@ -99,7 +76,30 @@ fn frame_snippets(ruby: &Ruby, entries: RArray) -> Result<Vec<u8>, MagnusError>
|
|
|
99
76
|
}
|
|
100
77
|
});
|
|
101
78
|
}
|
|
102
|
-
Ok(
|
|
79
|
+
Ok(snippets)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// One owner for the Preamble every guest entry reads, so an invocation
|
|
83
|
+
/// and a compile cannot drift in what they send.
|
|
84
|
+
fn preamble_from(
|
|
85
|
+
ruby: &Ruby,
|
|
86
|
+
paths: Vec<String>,
|
|
87
|
+
snippets: RArray,
|
|
88
|
+
flags: RArray,
|
|
89
|
+
) -> Result<Preamble, MagnusError> {
|
|
90
|
+
let mut preamble = Preamble::default();
|
|
91
|
+
preamble.bindings = Bindings { paths };
|
|
92
|
+
preamble.snippets = snippets_from(ruby, snippets)?;
|
|
93
|
+
preamble.flags = flags
|
|
94
|
+
.to_vec::<(String, String, bool)>()?
|
|
95
|
+
.into_iter()
|
|
96
|
+
.map(|(name, value, critical)| Flag {
|
|
97
|
+
name,
|
|
98
|
+
value,
|
|
99
|
+
critical,
|
|
100
|
+
})
|
|
101
|
+
.collect();
|
|
102
|
+
Ok(preamble)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// ---------------------------------------------------------------------------
|
|
@@ -113,9 +113,10 @@ pub fn init(ruby: &Ruby, kobako: RModule) -> Result<(), MagnusError> {
|
|
|
113
113
|
|
|
114
114
|
let runtime = kobako.define_class("Runtime", ruby.class_object())?;
|
|
115
115
|
runtime.define_singleton_method("from_path", function!(Runtime::from_path, 7))?;
|
|
116
|
-
runtime.define_method("eval", method!(Runtime::eval,
|
|
117
|
-
runtime.define_method("run", method!(Runtime::run,
|
|
116
|
+
runtime.define_method("eval", method!(Runtime::eval, 5))?;
|
|
117
|
+
runtime.define_method("run", method!(Runtime::run, 6))?;
|
|
118
118
|
runtime.define_method("profile", method!(Runtime::profile, 0))?;
|
|
119
|
+
runtime.define_method("compile", method!(Runtime::compile, 3))?;
|
|
119
120
|
// The guest re-enters for a block yield through a frame-scoped
|
|
120
121
|
// `Kobako::Runtime::GuestYielder` the dispatcher hands the Proc, not a
|
|
121
122
|
// method on Runtime.
|
|
@@ -124,9 +125,7 @@ pub fn init(ruby: &Ruby, kobako: RModule) -> Result<(), MagnusError> {
|
|
|
124
125
|
// Snapshot — the per-invocation result object each entry point returns.
|
|
125
126
|
let snapshot = runtime.define_class("Snapshot", ruby.class_object())?;
|
|
126
127
|
snapshot.define_method("outcome", method!(Snapshot::outcome, 0))?;
|
|
127
|
-
snapshot.define_method("
|
|
128
|
-
snapshot.define_method("trap_kind", method!(Snapshot::trap_kind, 0))?;
|
|
129
|
-
snapshot.define_method("trap_message", method!(Snapshot::trap_message, 0))?;
|
|
128
|
+
snapshot.define_method("trap_error", method!(Snapshot::trap_error, 0))?;
|
|
130
129
|
snapshot.define_method("wall_time", method!(Snapshot::wall_time, 0))?;
|
|
131
130
|
snapshot.define_method("memory_peak", method!(Snapshot::memory_peak, 0))?;
|
|
132
131
|
snapshot.define_method("stdout", method!(Snapshot::stdout, 0))?;
|
|
@@ -157,22 +156,8 @@ struct Runtime {
|
|
|
157
156
|
impl DataTypeFunctions for Runtime {}
|
|
158
157
|
|
|
159
158
|
impl Runtime {
|
|
160
|
-
///
|
|
161
|
-
///
|
|
162
|
-
/// Ruby-facing constructor for `Kobako::Runtime` — Engine and Module
|
|
163
|
-
/// are never visible to Ruby.
|
|
164
|
-
///
|
|
165
|
-
/// `timeout_seconds` is the wall-clock cap in seconds
|
|
166
|
-
/// (`None` disables); `memory_limit` is the linear-memory cap in
|
|
167
|
-
/// bytes (`None` disables); `stdout_limit` / `stderr_limit`
|
|
168
|
-
/// are the per-channel output caps (`None`
|
|
169
|
-
/// disables); `profile` is the isolation rung the driver builds
|
|
170
|
-
/// (`:permissive` / `:hermetic`); `gvl` is the scheduling mode
|
|
171
|
-
/// (`:hold` / `:release`) deciding whether each invocation releases
|
|
172
|
-
/// the GVL for its guest span. All six are validated by the caller
|
|
173
|
-
/// (`Kobako::Sandbox`); this method only refuses non-finite or
|
|
174
|
-
/// non-positive timeouts, off-ladder profiles, and unrecognized gvl
|
|
175
|
-
/// modes as a defence in depth.
|
|
159
|
+
/// The only Ruby-facing constructor, so Engine and Module are never
|
|
160
|
+
/// visible to Ruby.
|
|
176
161
|
fn from_path(
|
|
177
162
|
path: String,
|
|
178
163
|
timeout_seconds: Option<f64>,
|
|
@@ -246,90 +231,103 @@ impl Runtime {
|
|
|
246
231
|
// Run-path methods. Each takes the run's dispatch handler as its first
|
|
247
232
|
// argument and returns a `Snapshot` for any completed invocation —
|
|
248
233
|
// success or trap alike. Only a could-not-start fault (a missing export
|
|
249
|
-
// or a fault before the export call) raises
|
|
250
|
-
//
|
|
234
|
+
// or a fault before the export call) raises directly, as the class
|
|
235
|
+
// `errors::to_magnus` gives its cause, since it yields no `Snapshot`.
|
|
251
236
|
// -----------------------------------------------------------------
|
|
252
237
|
|
|
253
|
-
/// One-shot mruby source execution (`#eval`). Builds the dispatch
|
|
254
|
-
/// handler from `dispatch` (the per-invocation Proc), frames the two
|
|
255
|
-
/// stdin invocation frames from the registry state (`paths`,
|
|
256
|
-
/// `snippets`), hands them and the source to the driver, and returns
|
|
257
|
-
/// the run's `Snapshot`.
|
|
258
238
|
fn eval(
|
|
259
239
|
&self,
|
|
260
240
|
dispatch: Value,
|
|
261
241
|
paths: RArray,
|
|
262
|
-
source: RString,
|
|
263
242
|
snippets: RArray,
|
|
243
|
+
flags: RArray,
|
|
244
|
+
source: RString,
|
|
264
245
|
) -> Result<Snapshot, MagnusError> {
|
|
265
|
-
let ruby = Ruby::get().expect("Ruby thread");
|
|
266
|
-
let handler = build_handler(dispatch);
|
|
267
|
-
let preamble = frame_preamble(paths)?;
|
|
268
246
|
let source = rstring_to_vec(source);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Frames {
|
|
277
|
-
preamble: &preamble,
|
|
278
|
-
snippets: &snippets,
|
|
279
|
-
},
|
|
280
|
-
handler,
|
|
281
|
-
)
|
|
282
|
-
});
|
|
283
|
-
let snapshot = result.map_err(|e| errors::to_magnus(&ruby, e))?;
|
|
284
|
-
Ok(Snapshot::from(snapshot))
|
|
247
|
+
self.invoke(
|
|
248
|
+
dispatch,
|
|
249
|
+
paths,
|
|
250
|
+
snippets,
|
|
251
|
+
flags,
|
|
252
|
+
Entry::Eval { source: &source },
|
|
253
|
+
)
|
|
285
254
|
}
|
|
286
255
|
|
|
287
|
-
/// Execute one entrypoint dispatch (`__kobako_run`) and return its
|
|
288
|
-
/// `Snapshot`.
|
|
289
|
-
///
|
|
290
|
-
/// The two-frame stdin protocol (preamble + snippets; no user source
|
|
291
|
-
/// frame — docs/wire-codec.md § Invocation channels) plus the Run
|
|
292
|
-
/// envelope copied into guest linear memory — `entrypoint` routes it,
|
|
293
|
-
/// `payload` feeds it, and this side frames the two together. Cap
|
|
294
|
-
/// semantics match `#eval`.
|
|
295
256
|
fn run(
|
|
296
257
|
&self,
|
|
297
258
|
dispatch: Value,
|
|
298
259
|
paths: RArray,
|
|
299
260
|
snippets: RArray,
|
|
261
|
+
flags: RArray,
|
|
300
262
|
entrypoint: String,
|
|
301
263
|
payload: RString,
|
|
264
|
+
) -> Result<Snapshot, MagnusError> {
|
|
265
|
+
self.invoke(
|
|
266
|
+
dispatch,
|
|
267
|
+
paths,
|
|
268
|
+
snippets,
|
|
269
|
+
flags,
|
|
270
|
+
Entry::Run {
|
|
271
|
+
entrypoint: &entrypoint,
|
|
272
|
+
payload: rstring_to_vec(payload),
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// One owner for the wiring both verbs share, so a Preamble or handler
|
|
278
|
+
/// change cannot drift between them.
|
|
279
|
+
fn invoke(
|
|
280
|
+
&self,
|
|
281
|
+
dispatch: Value,
|
|
282
|
+
paths: RArray,
|
|
283
|
+
snippets: RArray,
|
|
284
|
+
flags: RArray,
|
|
285
|
+
entry: Entry<'_>,
|
|
302
286
|
) -> Result<Snapshot, MagnusError> {
|
|
303
287
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
288
|
+
let verb = match entry {
|
|
289
|
+
Entry::Eval { .. } => "eval",
|
|
290
|
+
Entry::Run { .. } => "run",
|
|
291
|
+
};
|
|
304
292
|
let handler = build_handler(dispatch);
|
|
305
|
-
let preamble =
|
|
306
|
-
let snippets = frame_snippets(&ruby, snippets)?;
|
|
307
|
-
let envelope = Run {
|
|
308
|
-
entrypoint,
|
|
309
|
-
payload: rstring_to_vec(payload),
|
|
310
|
-
}
|
|
311
|
-
.encode();
|
|
293
|
+
let preamble = preamble_from(&ruby, paths.to_vec()?, snippets, flags)?;
|
|
312
294
|
// Release the GVL around the guest span iff this Sandbox asks for it;
|
|
313
|
-
//
|
|
295
|
+
// the closure touches no Ruby VALUE (the driver is magnus-free, and a
|
|
296
|
+
// guest→host dispatch re-acquires the GVL through the bridge).
|
|
314
297
|
let result = gvl::region(self.release_gvl, || {
|
|
315
|
-
self.driver.invoke(
|
|
316
|
-
Entry::Run {
|
|
317
|
-
envelope: &envelope,
|
|
318
|
-
},
|
|
319
|
-
Frames {
|
|
320
|
-
preamble: &preamble,
|
|
321
|
-
snippets: &snippets,
|
|
322
|
-
},
|
|
323
|
-
handler,
|
|
324
|
-
)
|
|
298
|
+
self.driver.invoke(entry, &preamble, handler)
|
|
325
299
|
});
|
|
326
|
-
|
|
327
|
-
|
|
300
|
+
result
|
|
301
|
+
.map(|snapshot| Snapshot(snapshot, verb))
|
|
302
|
+
.map_err(|e| errors::to_magnus(&ruby, e))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// The bytecode of the source snippets, one binary String each in
|
|
306
|
+
/// order, or `nil` when the guest compiled nothing; no failure raises,
|
|
307
|
+
/// since the Sandbox keeps the source either way. Takes the Preamble an
|
|
308
|
+
/// invocation sends, `paths` included, so the driver can tell whether
|
|
309
|
+
/// the bytecode still fits it; no dispatch handler is bound, since
|
|
310
|
+
/// compiling runs no script.
|
|
311
|
+
fn compile(
|
|
312
|
+
&self,
|
|
313
|
+
paths: RArray,
|
|
314
|
+
snippets: RArray,
|
|
315
|
+
flags: RArray,
|
|
316
|
+
) -> Result<Option<RArray>, MagnusError> {
|
|
317
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
318
|
+
let preamble = preamble_from(&ruby, paths.to_vec()?, snippets, flags)?;
|
|
319
|
+
let bytecodes = gvl::region(self.release_gvl, || self.driver.compile(&preamble));
|
|
320
|
+
Ok(bytecodes.map(|bytecodes| {
|
|
321
|
+
ruby.ary_from_iter(
|
|
322
|
+
bytecodes
|
|
323
|
+
.iter()
|
|
324
|
+
.map(|bytecode| ruby.str_from_slice(bytecode)),
|
|
325
|
+
)
|
|
326
|
+
}))
|
|
328
327
|
}
|
|
329
328
|
|
|
330
|
-
///
|
|
331
|
-
///
|
|
332
|
-
/// compares against the posture its `profile:` option requested.
|
|
329
|
+
/// The driver's declaration, which the Sandbox checks against the
|
|
330
|
+
/// posture its `profile:` option requested.
|
|
333
331
|
fn profile(&self) -> Symbol {
|
|
334
332
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
335
333
|
match self.driver.profile() {
|
|
@@ -339,11 +337,9 @@ impl Runtime {
|
|
|
339
337
|
}
|
|
340
338
|
}
|
|
341
339
|
|
|
342
|
-
///
|
|
343
|
-
///
|
|
344
|
-
///
|
|
345
|
-
/// argument on the Ruby stack, so the driver only borrows it (the safety
|
|
346
|
-
/// contract on `kobako_runtime::runtime::Runtime`).
|
|
340
|
+
/// The Proc stays GC-rooted for the synchronous `#eval` / `#run` call as a
|
|
341
|
+
/// live method argument on the Ruby stack, so the driver only borrows it
|
|
342
|
+
/// (the safety contract on `kobako_runtime::runtime::Runtime`).
|
|
347
343
|
fn build_handler(dispatch: Value) -> Option<Arc<dyn DispatchHandler>> {
|
|
348
344
|
if dispatch.is_nil() {
|
|
349
345
|
return None;
|
|
@@ -358,130 +354,91 @@ fn build_handler(dispatch: Value) -> Option<Arc<dyn DispatchHandler>> {
|
|
|
358
354
|
/// driver produced, exposed as `Kobako::Runtime::Snapshot`. Usage and the
|
|
359
355
|
/// two output captures are present on every outcome, so the trap path
|
|
360
356
|
/// carries them just like the value path; the completion is read as either
|
|
361
|
-
/// the
|
|
362
|
-
///
|
|
363
|
-
/// `Kobako::TrapError` family.
|
|
357
|
+
/// the Outcome (`#outcome`) or a trap (`#trap_error`). The verb names the
|
|
358
|
+
/// invocation, so an unsupported one says which operation is lacking.
|
|
364
359
|
#[derive(TypedData)]
|
|
365
360
|
#[magnus(class = "Kobako::Runtime::Snapshot", free_immediately, size)]
|
|
366
|
-
struct Snapshot
|
|
367
|
-
completion: Completion,
|
|
368
|
-
stdout: Capture,
|
|
369
|
-
stderr: Capture,
|
|
370
|
-
usage: Usage,
|
|
371
|
-
}
|
|
361
|
+
struct Snapshot(RuntimeSnapshot, &'static str);
|
|
372
362
|
|
|
373
363
|
impl DataTypeFunctions for Snapshot {}
|
|
374
364
|
|
|
375
|
-
impl From<RuntimeSnapshot> for Snapshot {
|
|
376
|
-
fn from(snapshot: RuntimeSnapshot) -> Self {
|
|
377
|
-
let RuntimeSnapshot {
|
|
378
|
-
completion,
|
|
379
|
-
stdout,
|
|
380
|
-
stderr,
|
|
381
|
-
usage,
|
|
382
|
-
} = snapshot;
|
|
383
|
-
Self {
|
|
384
|
-
completion,
|
|
385
|
-
stdout,
|
|
386
|
-
stderr,
|
|
387
|
-
usage,
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
365
|
impl Snapshot {
|
|
393
|
-
///
|
|
394
|
-
///
|
|
395
|
-
///
|
|
396
|
-
///
|
|
397
|
-
|
|
398
|
-
/// answers empty. `panic` carries the Panic's own fields on `:panic`
|
|
399
|
-
/// and is `nil` otherwise, so the Ruby side maps a failure onto its
|
|
400
|
-
/// error taxonomy without decoding a payload byte.
|
|
401
|
-
///
|
|
402
|
-
/// A trap answers `:absent` — `#trapped?` is the authoritative
|
|
403
|
-
/// discriminator there and this is never read.
|
|
404
|
-
fn outcome(&self) -> (Symbol, RString, Option<PanicFields>) {
|
|
366
|
+
/// Already split off the core envelope, so the Ruby side maps a failure
|
|
367
|
+
/// onto its error taxonomy without decoding a payload byte: the ok
|
|
368
|
+
/// arm's payload, or a Panic's fields beside an empty payload. A trap
|
|
369
|
+
/// has no Outcome; `#trap_error` is the discriminator there.
|
|
370
|
+
fn outcome(&self) -> Result<(RString, Option<PanicFields>), MagnusError> {
|
|
405
371
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
match Outcome::decode(bytes) {
|
|
411
|
-
Ok(Outcome::Ok(value)) => (ruby.to_symbol("ok"), ruby.str_from_slice(&value), None),
|
|
412
|
-
Ok(Outcome::Panic(panic)) => (
|
|
413
|
-
ruby.to_symbol("panic"),
|
|
414
|
-
empty(),
|
|
372
|
+
match &self.0.completion {
|
|
373
|
+
Completion::Outcome(Outcome::Ok(value)) => Ok((ruby.str_from_slice(value), None)),
|
|
374
|
+
Completion::Outcome(Outcome::Panic(panic)) => Ok((
|
|
375
|
+
ruby.str_from_slice(&[]),
|
|
415
376
|
Some((
|
|
416
377
|
panic.origin.name().to_owned(),
|
|
417
|
-
panic.error.name,
|
|
418
|
-
panic.error.message,
|
|
419
|
-
panic.error.backtrace,
|
|
420
|
-
panic.available,
|
|
378
|
+
panic.error.name.clone(),
|
|
379
|
+
panic.error.message.clone(),
|
|
380
|
+
panic.error.backtrace.clone(),
|
|
381
|
+
panic.available.clone(),
|
|
421
382
|
)),
|
|
422
|
-
),
|
|
423
|
-
|
|
424
|
-
|
|
383
|
+
)),
|
|
384
|
+
// The Guest Binary answered rather than broke, so it reads as
|
|
385
|
+
// the Sandbox's failure, worded by the host.
|
|
386
|
+
Completion::Outcome(Outcome::Unsupported(_)) => Ok((
|
|
387
|
+
ruby.str_from_slice(&[]),
|
|
388
|
+
Some((
|
|
389
|
+
"sandbox".to_owned(),
|
|
390
|
+
"Kobako::SandboxError".to_owned(),
|
|
391
|
+
format!(
|
|
392
|
+
"the Sandbox runtime does not support #{}; \
|
|
393
|
+
rebuild it against the kobako version in use",
|
|
394
|
+
self.1
|
|
395
|
+
),
|
|
396
|
+
Vec::new(),
|
|
397
|
+
Vec::new(),
|
|
398
|
+
)),
|
|
399
|
+
)),
|
|
400
|
+
Completion::Trap(_) => Err(MagnusError::new(
|
|
401
|
+
ruby.exception_runtime_error(),
|
|
402
|
+
"a trapped run has no outcome",
|
|
403
|
+
)),
|
|
425
404
|
}
|
|
426
405
|
}
|
|
427
406
|
|
|
428
|
-
///
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
/// `:trap`), or `nil` on a completed run. The Sandbox maps this onto the
|
|
435
|
-
/// named `Kobako::TrapError` subclass.
|
|
436
|
-
fn trap_kind(&self) -> Option<Symbol> {
|
|
407
|
+
/// Unraised, so the Sandbox layer can attach the run's Execution before
|
|
408
|
+
/// raising it.
|
|
409
|
+
fn trap_error(&self) -> Result<Option<Exception>, MagnusError> {
|
|
410
|
+
let Completion::Trap(trap) = &self.0.completion else {
|
|
411
|
+
return Ok(None);
|
|
412
|
+
};
|
|
437
413
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// A cap this frontend has no named subclass for is still a
|
|
442
|
-
// trap; the base class is what the taxonomy owes it.
|
|
443
|
-
Completion::Trap(_) => Some(ruby.to_symbol("trap")),
|
|
444
|
-
Completion::Outcome(_) => None,
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/// The trap's message, or `nil` on a completed run.
|
|
449
|
-
fn trap_message(&self) -> Option<String> {
|
|
450
|
-
match &self.completion {
|
|
451
|
-
Completion::Trap(trap) => Some(trap.to_string()),
|
|
452
|
-
Completion::Outcome(_) => None,
|
|
453
|
-
}
|
|
414
|
+
errors::trap_class(&ruby, trap)
|
|
415
|
+
.new_instance((trap.to_string(),))
|
|
416
|
+
.map(Some)
|
|
454
417
|
}
|
|
455
418
|
|
|
456
|
-
/// Wall-clock seconds the guest export call spent inside wasmtime.
|
|
457
419
|
fn wall_time(&self) -> f64 {
|
|
458
|
-
self.usage.wall_time
|
|
420
|
+
self.0.usage.wall_time
|
|
459
421
|
}
|
|
460
422
|
|
|
461
|
-
/// High-water `memory.grow` delta in bytes past the entry-time baseline.
|
|
462
423
|
fn memory_peak(&self) -> usize {
|
|
463
|
-
self.usage.memory_peak
|
|
424
|
+
self.0.usage.memory_peak
|
|
464
425
|
}
|
|
465
426
|
|
|
466
|
-
/// Bytes captured on the guest's stdout channel, clipped to the cap.
|
|
467
427
|
fn stdout(&self) -> RString {
|
|
468
428
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
469
|
-
ruby.str_from_slice(&self.stdout.bytes)
|
|
429
|
+
ruby.str_from_slice(&self.0.stdout.bytes)
|
|
470
430
|
}
|
|
471
431
|
|
|
472
|
-
/// `true` iff the stdout channel reached its cap during this run.
|
|
473
432
|
fn stdout_truncated(&self) -> bool {
|
|
474
|
-
self.stdout.truncated
|
|
433
|
+
self.0.stdout.truncated
|
|
475
434
|
}
|
|
476
435
|
|
|
477
|
-
/// Bytes captured on the guest's stderr channel, clipped to the cap.
|
|
478
436
|
fn stderr(&self) -> RString {
|
|
479
437
|
let ruby = Ruby::get().expect("Ruby thread");
|
|
480
|
-
ruby.str_from_slice(&self.stderr.bytes)
|
|
438
|
+
ruby.str_from_slice(&self.0.stderr.bytes)
|
|
481
439
|
}
|
|
482
440
|
|
|
483
|
-
/// `true` iff the stderr channel reached its cap during this run.
|
|
484
441
|
fn stderr_truncated(&self) -> bool {
|
|
485
|
-
self.stderr.truncated
|
|
442
|
+
self.0.stderr.truncated
|
|
486
443
|
}
|
|
487
444
|
}
|
data/lib/kobako/capture.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
|
-
|
|
5
|
-
# cap, together with whether it wrote past the cap. Frozen, so the bytes
|
|
6
|
-
# and the flag always travel together.
|
|
7
|
-
class Capture
|
|
4
|
+
class Capture # :nodoc:
|
|
8
5
|
attr_reader :bytes
|
|
9
6
|
|
|
10
7
|
# The bytes read as UTF-8 when they are valid UTF-8, and as binary
|