kobako 0.28.0 → 0.29.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 +170 -0
- data/Cargo.lock +111 -82
- data/README.md +154 -36
- data/ROADMAP.md +5 -0
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +86 -0
- data/crates/kobako-runtime/Cargo.toml +27 -6
- data/crates/kobako-runtime/README.md +13 -8
- data/crates/kobako-runtime/src/dispatch.rs +8 -0
- data/crates/kobako-runtime/src/filesystem/directory/spelling.rs +194 -0
- data/crates/kobako-runtime/src/filesystem/directory.rs +1082 -0
- data/crates/kobako-runtime/src/filesystem/memory.rs +1100 -0
- data/crates/kobako-runtime/src/filesystem/overlay/exclusive.rs +88 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +808 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +1955 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +60 -0
- data/crates/kobako-runtime/src/filesystem/temporary.rs +167 -0
- data/crates/kobako-runtime/src/filesystem.rs +392 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/lib.rs +11 -1
- data/crates/kobako-runtime/src/mount.rs +178 -0
- data/crates/kobako-runtime/src/profile.rs +4 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-transport/CHANGELOG.md +17 -0
- data/crates/kobako-transport/Cargo.toml +1 -1
- data/crates/kobako-transport/README.md +1 -1
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/fault.rs +2 -1
- data/crates/kobako-transport/src/envelope/preamble.rs +12 -12
- data/crates/kobako-transport/src/envelope/reply.rs +32 -14
- data/crates/kobako-transport/src/envelope/request.rs +18 -8
- data/crates/kobako-wasmtime/CHANGELOG.md +119 -0
- data/crates/kobako-wasmtime/Cargo.toml +21 -16
- data/crates/kobako-wasmtime/README.md +5 -3
- data/crates/kobako-wasmtime/src/abi.rs +66 -32
- data/crates/kobako-wasmtime/src/ambient.rs +63 -4
- data/crates/kobako-wasmtime/src/cache.rs +185 -74
- data/crates/kobako-wasmtime/src/config.rs +58 -1
- data/crates/kobako-wasmtime/src/descriptors.rs +167 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +47 -29
- data/crates/kobako-wasmtime/src/driver.rs +46 -44
- data/crates/kobako-wasmtime/src/guest_mem.rs +13 -7
- data/crates/kobako-wasmtime/src/imports.rs +46 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +17 -45
- data/crates/kobako-wasmtime/src/invocation.rs +35 -46
- data/crates/kobako-wasmtime/src/io.rs +5 -202
- data/crates/kobako-wasmtime/src/lib.rs +4 -0
- data/crates/kobako-wasmtime/src/preview1/args.rs +60 -0
- data/crates/kobako-wasmtime/src/preview1/beneath.rs +188 -0
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +609 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +372 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +267 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +672 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +205 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +2499 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +421 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +333 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +30 -0
- data/crates/kobako-wasmtime/src/preview1.rs +461 -0
- data/crates/kobako-wasmtime/src/trap.rs +39 -19
- data/crates/kobako-wasmtime/src/wasi.rs +317 -0
- data/crates/kobako-wasmtime/tests/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/guest_file.rs +1054 -0
- data/crates/kobako-wasmtime/tests/preamble_flags.rs +3 -2
- data/crates/kobako-wasmtime/tests/snippet_compile.rs +4 -3
- data/crates/kobako-wasmtime/tests/support/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/unsupported_call.rs +1 -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/escape.rs +53 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +91 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +400 -0
- data/ext/kobako/src/runtime/filesystem.rs +236 -0
- data/ext/kobako/src/runtime/gvl.rs +7 -1
- 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 +120 -199
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/mount.rb +27 -0
- data/lib/kobako/catalog/services.rb +11 -0
- data/lib/kobako/catalog/snippets.rb +5 -9
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +1 -0
- data/lib/kobako/codec/ext_types.rb +1 -1
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +38 -11
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +38 -0
- data/lib/kobako/pool.rb +2 -2
- data/lib/kobako/sandbox.rb +40 -12
- data/lib/kobako/sandbox_options.rb +39 -12
- data/lib/kobako/snippet/{binary.rb → bytecode.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 +30 -26
- data/lib/kobako/transport/exposure.rb +5 -7
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/yielder.rb +8 -30
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/mount.rbs +28 -0
- data/sig/kobako/catalog/services.rbs +4 -0
- data/sig/kobako/catalog/snippets.rbs +1 -1
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +2 -0
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +88 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/pool.rbs +1 -1
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +7 -1
- data/sig/kobako/sandbox_options.rbs +24 -2
- 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 +3 -3
- data/sig/kobako/transport/exposure.rbs +1 -1
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +2 -2
- metadata +60 -7
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//! The Ruby face of the Filesystem: `Kobako::Filesystem::Memory`,
|
|
2
|
+
//! `Temporary`, `Directory`, and `Overlay`, each standing for the
|
|
3
|
+
//! `kobako_runtime` Filesystem of that kind.
|
|
4
|
+
//!
|
|
5
|
+
//! A kind kobako ships runs in Rust; Ruby only picks it and the access.
|
|
6
|
+
//! Ruby is asked again while the guest runs through a mount's block, at
|
|
7
|
+
//! most once per open or change, and through a Filesystem written in
|
|
8
|
+
//! Ruby, once per call.
|
|
9
|
+
|
|
10
|
+
mod grants;
|
|
11
|
+
mod ruby;
|
|
12
|
+
|
|
13
|
+
use std::sync::Arc;
|
|
14
|
+
|
|
15
|
+
use magnus::value::{Opaque, ReprValue};
|
|
16
|
+
use magnus::{
|
|
17
|
+
function, gc, method, prelude::*, scan_args::get_kwargs, scan_args::scan_args,
|
|
18
|
+
DataTypeFunctions, Error as MagnusError, RHash, RModule, RString, Ruby, Symbol, TypedData,
|
|
19
|
+
Value,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
use ruby::RubyFilesystem;
|
|
23
|
+
|
|
24
|
+
use kobako_runtime::filesystem::{
|
|
25
|
+
Access, Blocking, Directory, Filesystem, Memory, Overlay, Temporary,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
pub(super) use grants::{to_grants, validate_mount};
|
|
29
|
+
|
|
30
|
+
macro_rules! filesystem_class {
|
|
31
|
+
($name:ident, $class:literal) => {
|
|
32
|
+
#[magnus::wrap(class = $class, free_immediately, size)]
|
|
33
|
+
pub(super) struct $name(Arc<dyn Filesystem>);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
filesystem_class!(MemoryFs, "Kobako::Filesystem::Memory");
|
|
38
|
+
filesystem_class!(TemporaryFs, "Kobako::Filesystem::Temporary");
|
|
39
|
+
filesystem_class!(DirectoryFs, "Kobako::Filesystem::Directory");
|
|
40
|
+
|
|
41
|
+
/// The Overlay itself rather than a shared handle, so `layer` can answer a
|
|
42
|
+
/// new one and leave this one as it was. It marks the Ruby object of
|
|
43
|
+
/// every Layer, since a Filesystem written in Ruby has nothing else
|
|
44
|
+
/// keeping it alive.
|
|
45
|
+
#[derive(magnus::TypedData)]
|
|
46
|
+
#[magnus(class = "Kobako::Filesystem::Overlay", free_immediately, size, mark)]
|
|
47
|
+
pub(super) struct OverlayFs {
|
|
48
|
+
overlay: Overlay,
|
|
49
|
+
layers: Vec<Opaque<Value>>,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl DataTypeFunctions for OverlayFs {
|
|
53
|
+
fn mark(&self, marker: &gc::Marker) {
|
|
54
|
+
marker.mark_slice(&self.layers);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub(super) fn register(ruby: &Ruby, kobako: RModule) -> Result<(), MagnusError> {
|
|
59
|
+
let module = kobako.define_module("Filesystem")?;
|
|
60
|
+
let memory = module.define_class("Memory", ruby.class_object())?;
|
|
61
|
+
memory.define_singleton_method("new", function!(memory_new, -1))?;
|
|
62
|
+
let temporary = module.define_class("Temporary", ruby.class_object())?;
|
|
63
|
+
temporary.define_singleton_method("new", function!(temporary_new, -1))?;
|
|
64
|
+
let directory = module.define_class("Directory", ruby.class_object())?;
|
|
65
|
+
directory.define_singleton_method("new", function!(directory_new, 1))?;
|
|
66
|
+
let overlay = module.define_class("Overlay", ruby.class_object())?;
|
|
67
|
+
overlay.define_singleton_method("new", function!(overlay_new, 0))?;
|
|
68
|
+
overlay.define_method("layer", method!(overlay_layer, -1))?;
|
|
69
|
+
MemoryFs::class(ruby);
|
|
70
|
+
TemporaryFs::class(ruby);
|
|
71
|
+
DirectoryFs::class(ruby);
|
|
72
|
+
OverlayFs::class(ruby);
|
|
73
|
+
Ok(())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// `Memory.new(files = {}, limit: 64 MiB, inodes:)`: each
|
|
77
|
+
/// `path => contents` pair becomes a file, its directories created along
|
|
78
|
+
/// the way. Ruby hands a bare `"path" => contents` over as keywords, so
|
|
79
|
+
/// every String keyword is a file too. Without `inodes:` the entries
|
|
80
|
+
/// follow the limit.
|
|
81
|
+
fn memory_new(args: &[Value]) -> Result<MemoryFs, MagnusError> {
|
|
82
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
83
|
+
let args = scan_args::<(), (Option<RHash>,), (), (), RHash, ()>(args)?;
|
|
84
|
+
let mut files = Vec::new();
|
|
85
|
+
let mut limit = Some(Memory::DEFAULT_LIMIT);
|
|
86
|
+
let mut inodes = None;
|
|
87
|
+
let limit_key = ruby.to_symbol("limit");
|
|
88
|
+
let inodes_key = ruby.to_symbol("inodes");
|
|
89
|
+
for hash in args.optional.0.into_iter().chain([args.keywords]) {
|
|
90
|
+
hash.foreach(|key: Value, value: Value| {
|
|
91
|
+
match Symbol::from_value(key) {
|
|
92
|
+
Some(key) if key.equal(limit_key)? => limit = to_limit(value)?,
|
|
93
|
+
Some(key) if key.equal(inodes_key)? => inodes = Some(to_limit(value)?),
|
|
94
|
+
Some(key) => {
|
|
95
|
+
return Err(MagnusError::new(
|
|
96
|
+
ruby.exception_arg_error(),
|
|
97
|
+
format!("unknown keyword: :{}", key.name()?),
|
|
98
|
+
))
|
|
99
|
+
}
|
|
100
|
+
None => files.push((
|
|
101
|
+
String::try_convert(key)?,
|
|
102
|
+
super::rstring_to_vec(RString::try_convert(value)?),
|
|
103
|
+
)),
|
|
104
|
+
}
|
|
105
|
+
Ok(magnus::r_hash::ForEach::Continue)
|
|
106
|
+
})?;
|
|
107
|
+
}
|
|
108
|
+
// A list no Memory holds is an `ArgumentError` the Host App can rescue.
|
|
109
|
+
let memory = Memory::try_from_iter(files)
|
|
110
|
+
.map_err(|error| MagnusError::new(ruby.exception_arg_error(), error.to_string()))?
|
|
111
|
+
.with_limit(limit);
|
|
112
|
+
Ok(MemoryFs(Arc::new(match inodes {
|
|
113
|
+
Some(inodes) => memory.with_inodes(inodes),
|
|
114
|
+
None => memory,
|
|
115
|
+
})))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// `Temporary.new(limit: 64 MiB, inodes:)`.
|
|
119
|
+
fn temporary_new(args: &[Value]) -> Result<TemporaryFs, MagnusError> {
|
|
120
|
+
let args = scan_args::<(), (), (), (), RHash, ()>(args)?;
|
|
121
|
+
let kwargs = get_kwargs::<_, (), (Option<Value>, Option<Value>), ()>(
|
|
122
|
+
args.keywords,
|
|
123
|
+
&[],
|
|
124
|
+
&["limit", "inodes"],
|
|
125
|
+
)?;
|
|
126
|
+
let (limit, inodes) = kwargs.optional;
|
|
127
|
+
let limit = match limit {
|
|
128
|
+
Some(limit) => to_limit(limit)?,
|
|
129
|
+
None => Some(Memory::DEFAULT_LIMIT),
|
|
130
|
+
};
|
|
131
|
+
let temporary = Temporary::new().with_limit(limit);
|
|
132
|
+
Ok(TemporaryFs(Arc::new(match inodes {
|
|
133
|
+
Some(inodes) => temporary.with_inodes(to_limit(inodes)?),
|
|
134
|
+
None => temporary,
|
|
135
|
+
})))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// A `limit:` or `inodes:` count, or `nil` for none.
|
|
139
|
+
fn to_limit(limit: Value) -> Result<Option<usize>, MagnusError> {
|
|
140
|
+
Option::<usize>::try_convert(limit)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn directory_new(path: String) -> Result<DirectoryFs, MagnusError> {
|
|
144
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
145
|
+
Directory::new(&path)
|
|
146
|
+
.map(|directory| DirectoryFs(Arc::new(directory)))
|
|
147
|
+
.map_err(|err| {
|
|
148
|
+
MagnusError::new(
|
|
149
|
+
ruby.exception_arg_error(),
|
|
150
|
+
format!("cannot open host directory {path}: {err}"),
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// `Overlay.new`, with no Layer yet.
|
|
156
|
+
fn overlay_new() -> OverlayFs {
|
|
157
|
+
OverlayFs {
|
|
158
|
+
overlay: Overlay::new().with_blocking(Arc::new(GvlFreeWait)),
|
|
159
|
+
layers: Vec::new(),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// An Overlay's thread waits for its lock without the GVL, so a holder
|
|
164
|
+
/// asking a Layer written in Ruby can take the GVL and finish.
|
|
165
|
+
struct GvlFreeWait;
|
|
166
|
+
|
|
167
|
+
impl Blocking for GvlFreeWait {
|
|
168
|
+
fn wait(&self, wait: &mut dyn FnMut()) {
|
|
169
|
+
super::gvl::region(super::gvl::holds(), wait);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// `layer(filesystem, access: :read)`, a new Overlay with `filesystem`
|
|
174
|
+
/// beneath this one's Layers.
|
|
175
|
+
fn overlay_layer(overlay: &OverlayFs, args: &[Value]) -> Result<OverlayFs, MagnusError> {
|
|
176
|
+
let args = scan_args::<(Value,), (), (), (), RHash, ()>(args)?;
|
|
177
|
+
let kwargs = get_kwargs::<_, (), (Option<Value>,), ()>(args.keywords, &[], &["access"])?;
|
|
178
|
+
let (filesystem,) = args.required;
|
|
179
|
+
let access = match kwargs.optional.0 {
|
|
180
|
+
Some(access) => to_access(access)?,
|
|
181
|
+
None => Access::Read,
|
|
182
|
+
};
|
|
183
|
+
let mut layers = overlay.layers.clone();
|
|
184
|
+
layers.push(Opaque::from(filesystem));
|
|
185
|
+
Ok(OverlayFs {
|
|
186
|
+
overlay: overlay
|
|
187
|
+
.overlay
|
|
188
|
+
.clone()
|
|
189
|
+
.layer(to_filesystem(filesystem)?, access),
|
|
190
|
+
layers,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn to_access(access: Value) -> Result<Access, MagnusError> {
|
|
195
|
+
let name = Symbol::from_value(access)
|
|
196
|
+
.map(|symbol| symbol.name())
|
|
197
|
+
.transpose()?;
|
|
198
|
+
match name.as_deref() {
|
|
199
|
+
Some("read") => Ok(Access::Read),
|
|
200
|
+
Some("read_write") => Ok(Access::ReadWrite),
|
|
201
|
+
_ => Err(MagnusError::new(
|
|
202
|
+
Ruby::get().expect("Ruby thread").exception_arg_error(),
|
|
203
|
+
format!(
|
|
204
|
+
"access must be :read or :read_write, got {}",
|
|
205
|
+
access.inspect()
|
|
206
|
+
),
|
|
207
|
+
)),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// The Filesystem a Ruby handle of any kobako kind stands for.
|
|
212
|
+
fn to_filesystem(value: Value) -> Result<Arc<dyn Filesystem>, MagnusError> {
|
|
213
|
+
if let Ok(fs) = <&MemoryFs>::try_convert(value) {
|
|
214
|
+
return Ok(fs.0.clone());
|
|
215
|
+
}
|
|
216
|
+
if let Ok(fs) = <&TemporaryFs>::try_convert(value) {
|
|
217
|
+
return Ok(fs.0.clone());
|
|
218
|
+
}
|
|
219
|
+
if let Ok(fs) = <&DirectoryFs>::try_convert(value) {
|
|
220
|
+
return Ok(fs.0.clone());
|
|
221
|
+
}
|
|
222
|
+
if let Ok(fs) = <&OverlayFs>::try_convert(value) {
|
|
223
|
+
return Ok(Arc::new(fs.overlay.clone()));
|
|
224
|
+
}
|
|
225
|
+
if let Some(fs) = RubyFilesystem::from_value(value) {
|
|
226
|
+
return Ok(Arc::new(fs));
|
|
227
|
+
}
|
|
228
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
229
|
+
Err(MagnusError::new(
|
|
230
|
+
ruby.exception_type_error(),
|
|
231
|
+
format!(
|
|
232
|
+
"expected a Kobako::Filesystem or an object answering open and stat, got {}",
|
|
233
|
+
value.class()
|
|
234
|
+
),
|
|
235
|
+
))
|
|
236
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Releasing Ruby's GVL for
|
|
1
|
+
//! Releasing Ruby's GVL for an invocation's guest span so distinct Sandboxes on
|
|
2
2
|
//! distinct Threads run their guest code in parallel.
|
|
3
3
|
//!
|
|
4
4
|
//! `region` brackets one invocation's guest span, releasing the GVL when the
|
|
@@ -35,6 +35,12 @@ where
|
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/// Whether this thread holds the GVL: outside any released region, or
|
|
39
|
+
/// inside a dispatch that re-acquired it.
|
|
40
|
+
pub(crate) fn holds() -> bool {
|
|
41
|
+
!GVL_RELEASED.with(|g| g.get())
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
/// Runs inline when the GVL is already held — hold mode, or a nested
|
|
39
45
|
/// dispatch whose outer frame re-acquired it.
|
|
40
46
|
pub(crate) fn reenter<F, R>(f: F) -> R
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//! A Ruby object Rust holds across calls into the guest, registered with
|
|
2
|
+
//! Ruby's collector so it is neither freed nor moved by compaction while
|
|
3
|
+
//! the holder lives.
|
|
4
|
+
|
|
5
|
+
use magnus::value::Opaque;
|
|
6
|
+
use magnus::{Ruby, Value};
|
|
7
|
+
|
|
8
|
+
use super::gvl;
|
|
9
|
+
|
|
10
|
+
pub(crate) struct PinnedValue(Box<Opaque<Value>>);
|
|
11
|
+
|
|
12
|
+
impl PinnedValue {
|
|
13
|
+
pub(crate) fn new(ruby: &Ruby, value: Value) -> Self {
|
|
14
|
+
let value = Box::new(Opaque::from(value));
|
|
15
|
+
ruby.gc_register_address(&*value);
|
|
16
|
+
Self(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub(crate) fn get(&self) -> Opaque<Value> {
|
|
20
|
+
*self.0
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Drop for PinnedValue {
|
|
25
|
+
fn drop(&mut self) {
|
|
26
|
+
gvl::reenter(|| {
|
|
27
|
+
let ruby = Ruby::get().expect("Ruby handle unavailable releasing a pinned object");
|
|
28
|
+
ruby.gc_unregister_address(&*self.0);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//! `Kobako::Snapshot`: one invocation's result at the Ruby boundary.
|
|
2
|
+
|
|
3
|
+
use magnus::{
|
|
4
|
+
method, prelude::*, typed_data::DataTypeFunctions, Error as MagnusError, Exception, RModule,
|
|
5
|
+
RString, Ruby, TypedData,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use kobako_runtime::snapshot::{Completion, Snapshot as RuntimeSnapshot};
|
|
9
|
+
use kobako_transport::envelope::Outcome;
|
|
10
|
+
|
|
11
|
+
use super::errors;
|
|
12
|
+
|
|
13
|
+
/// A Panic's fields as they cross to Ruby: origin, class, message,
|
|
14
|
+
/// backtrace, and the names the invocation could have used in place of the
|
|
15
|
+
/// one it named. Named so the Ruby side reads them positionally against one
|
|
16
|
+
/// shape, matching the `Kobako::Outcome::panic_fields` alias.
|
|
17
|
+
type PanicFields = (String, String, String, Vec<String>, Vec<String>);
|
|
18
|
+
|
|
19
|
+
pub(super) fn register(ruby: &Ruby, kobako: RModule) -> Result<(), MagnusError> {
|
|
20
|
+
let snapshot = kobako.define_class("Snapshot", ruby.class_object())?;
|
|
21
|
+
snapshot.define_method("outcome", method!(Snapshot::outcome, 0))?;
|
|
22
|
+
snapshot.define_method("trap_error", method!(Snapshot::trap_error, 0))?;
|
|
23
|
+
snapshot.define_method("wall_time", method!(Snapshot::wall_time, 0))?;
|
|
24
|
+
snapshot.define_method("memory_peak", method!(Snapshot::memory_peak, 0))?;
|
|
25
|
+
snapshot.define_method("stdout", method!(Snapshot::stdout, 0))?;
|
|
26
|
+
snapshot.define_method("stdout_truncated?", method!(Snapshot::stdout_truncated, 0))?;
|
|
27
|
+
snapshot.define_method("stderr", method!(Snapshot::stderr, 0))?;
|
|
28
|
+
snapshot.define_method("stderr_truncated?", method!(Snapshot::stderr_truncated, 0))?;
|
|
29
|
+
Ok(())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// One invocation's result at the Ruby boundary — the whole `Snapshot` the
|
|
33
|
+
/// driver produced, exposed as `Kobako::Snapshot`. Usage and the
|
|
34
|
+
/// two output captures are present on every outcome, so the trap path
|
|
35
|
+
/// carries them just like the value path; the completion is read as either
|
|
36
|
+
/// the Outcome (`#outcome`) or a trap (`#trap_error`). The verb names the
|
|
37
|
+
/// invocation, so an unsupported one says which operation is lacking.
|
|
38
|
+
#[derive(TypedData)]
|
|
39
|
+
#[magnus(class = "Kobako::Snapshot", free_immediately, size)]
|
|
40
|
+
pub(super) struct Snapshot(RuntimeSnapshot, &'static str);
|
|
41
|
+
|
|
42
|
+
impl DataTypeFunctions for Snapshot {}
|
|
43
|
+
|
|
44
|
+
impl Snapshot {
|
|
45
|
+
pub(super) fn new(snapshot: RuntimeSnapshot, verb: &'static str) -> Self {
|
|
46
|
+
Self(snapshot, verb)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Already split off the core envelope, so the Ruby side maps a failure
|
|
50
|
+
/// onto its error taxonomy without decoding a payload byte: the ok
|
|
51
|
+
/// arm's payload, or a Panic's fields beside an empty payload. A trap
|
|
52
|
+
/// has no Outcome; `#trap_error` is the discriminator there.
|
|
53
|
+
fn outcome(&self) -> Result<(RString, Option<PanicFields>), MagnusError> {
|
|
54
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
55
|
+
match &self.0.completion {
|
|
56
|
+
Completion::Outcome(Outcome::Ok(value)) => Ok((ruby.str_from_slice(value), None)),
|
|
57
|
+
Completion::Outcome(Outcome::Panic(panic)) => Ok((
|
|
58
|
+
ruby.str_from_slice(&[]),
|
|
59
|
+
Some((
|
|
60
|
+
panic.origin.name().to_owned(),
|
|
61
|
+
panic.error.name.clone(),
|
|
62
|
+
panic.error.message.clone(),
|
|
63
|
+
panic.error.backtrace.clone(),
|
|
64
|
+
panic.available.clone(),
|
|
65
|
+
)),
|
|
66
|
+
)),
|
|
67
|
+
// The Guest Binary answered rather than broke, so it reads as
|
|
68
|
+
// the Sandbox's failure, worded by the host.
|
|
69
|
+
Completion::Outcome(Outcome::Unsupported(_)) => Ok((
|
|
70
|
+
ruby.str_from_slice(&[]),
|
|
71
|
+
Some((
|
|
72
|
+
"sandbox".to_owned(),
|
|
73
|
+
"Kobako::SandboxError".to_owned(),
|
|
74
|
+
format!(
|
|
75
|
+
"the Sandbox runtime does not support #{}; \
|
|
76
|
+
rebuild it against the kobako version in use",
|
|
77
|
+
self.1
|
|
78
|
+
),
|
|
79
|
+
Vec::new(),
|
|
80
|
+
Vec::new(),
|
|
81
|
+
)),
|
|
82
|
+
)),
|
|
83
|
+
Completion::Trap(_) => Err(MagnusError::new(
|
|
84
|
+
ruby.exception_runtime_error(),
|
|
85
|
+
"a trapped run has no outcome",
|
|
86
|
+
)),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Unraised, so the Sandbox layer can attach the run's Execution before
|
|
91
|
+
/// raising it.
|
|
92
|
+
fn trap_error(&self) -> Result<Option<Exception>, MagnusError> {
|
|
93
|
+
let Completion::Trap(trap) = &self.0.completion else {
|
|
94
|
+
return Ok(None);
|
|
95
|
+
};
|
|
96
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
97
|
+
errors::trap_class(&ruby, trap)
|
|
98
|
+
.new_instance((trap.to_string(),))
|
|
99
|
+
.map(Some)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn wall_time(&self) -> f64 {
|
|
103
|
+
self.0.usage.wall_time
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn memory_peak(&self) -> usize {
|
|
107
|
+
self.0.usage.memory_peak
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn stdout(&self) -> RString {
|
|
111
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
112
|
+
ruby.str_from_slice(&self.0.stdout.bytes)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn stdout_truncated(&self) -> bool {
|
|
116
|
+
self.0.stdout.truncated
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn stderr(&self) -> RString {
|
|
120
|
+
let ruby = Ruby::get().expect("Ruby thread");
|
|
121
|
+
ruby.str_from_slice(&self.0.stderr.bytes)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn stderr_truncated(&self) -> bool {
|
|
125
|
+
self.0.stderr.truncated
|
|
126
|
+
}
|
|
127
|
+
}
|