kobako 0.19.0 → 0.20.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 +20 -0
- data/Cargo.lock +6 -5
- data/crates/kobako-runtime/CHANGELOG.md +7 -0
- data/crates/kobako-runtime/Cargo.toml +1 -1
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-wasmtime/CHANGELOG.md +7 -0
- data/crates/kobako-wasmtime/Cargo.toml +2 -2
- data/crates/kobako-wasmtime/README.md +1 -1
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +6 -2
- data/ext/kobako/src/runtime/bridge.rs +30 -24
- data/ext/kobako/src/runtime/gvl.rs +114 -0
- data/ext/kobako/src/runtime.rs +211 -190
- data/lib/kobako/catalog/extensions.rb +50 -43
- data/lib/kobako/catalog/handles.rb +7 -16
- data/lib/kobako/catalog/services.rb +22 -36
- data/lib/kobako/catalog/snippets.rb +11 -10
- data/lib/kobako/context.rb +202 -0
- data/lib/kobako/errors.rb +26 -4
- data/lib/kobako/execution.rb +52 -0
- data/lib/kobako/extension.rb +24 -10
- data/lib/kobako/pool.rb +1 -7
- data/lib/kobako/sandbox.rb +43 -182
- data/lib/kobako/sandbox_options.rb +23 -2
- data/lib/kobako/transport/dispatcher.rb +16 -15
- data/lib/kobako/unresolved.rb +17 -0
- data/lib/kobako/usage.rb +6 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/catalog/extensions.rbs +4 -6
- data/sig/kobako/catalog/handles.rbs +0 -2
- data/sig/kobako/catalog/services.rbs +3 -3
- data/sig/kobako/catalog/snippets.rbs +2 -0
- data/sig/kobako/context.rbs +36 -0
- data/sig/kobako/errors.rbs +12 -3
- data/sig/kobako/execution.rbs +24 -0
- data/sig/kobako/extension.rbs +4 -2
- data/sig/kobako/runtime.rbs +37 -9
- data/sig/kobako/sandbox.rbs +5 -24
- data/sig/kobako/sandbox_options.rbs +9 -2
- data/sig/kobako/transport/dispatcher.rbs +3 -3
- data/sig/kobako/unresolved.rbs +4 -0
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c3d1c75e0967f01b1284848372d66adda8cc65566b828bb1634f1624b1b4938
|
|
4
|
+
data.tar.gz: 223e0cc856970daf5c702775c1a3df2851d96beb72db1793e24c776747205a0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b6050756d4ed75616ea4aa82fe56febc11cc97c8b1cba165c607e70b6921dfad322c1594746b3e105338653fce93f5e208b4f2516c39f2f3cb4d9b85647b79b
|
|
7
|
+
data.tar.gz: 8c50704f755219097efdd41c5e47ba304fa98fe8843e5f39ee49df4c22cac7d7916cd8a96be472ffae3535d4f285422e0cca81917c722584b44165f262611212
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.20.0","wasm/kobako-core":"0.12.0","wasm/kobako-mruby":"0.12.0","wasm/kobako-io":"0.12.0","wasm/kobako-json":"0.12.0","wasm/kobako-regexp":"0.12.0","wasm/kobako-baker":"0.12.0","crates/kobako-codec":"0.12.0","crates/kobako-runtime":"0.12.0","crates/kobako-wasmtime":"0.12.0","crates/kobako":"0.12.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.0](https://github.com/elct9620/kobako/compare/v0.19.0...v0.20.0) (2026-07-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **bench:** mark a run in progress with a pid lock file ([3ca85fb](https://github.com/elct9620/kobako/commit/3ca85fbe9860ce32d58c1a5bf4ff85cbdc210780))
|
|
9
|
+
* **execution:** tell a nil-value success from a failure with #failed? ([2f3d382](https://github.com/elct9620/kobako/commit/2f3d382b24d65fabda7771429cd3adfc90a8c7e8))
|
|
10
|
+
* **extension:** declare a backend's kind by explicit keyword ([3f6c628](https://github.com/elct9620/kobako/commit/3f6c6283080ba4346ac8ef878e469dab8039e650))
|
|
11
|
+
* **sandbox:** add the gvl: hold/release scheduling option ([ca2a884](https://github.com/elct9620/kobako/commit/ca2a884415b3271c44b20d131536b3fe7c5a3077))
|
|
12
|
+
* **sandbox:** declare fillable Service paths defaulting to Kobako::Unresolved ([1d60f76](https://github.com/elct9620/kobako/commit/1d60f764ddff3b5b10206993e2b281c838ec0022))
|
|
13
|
+
* **sandbox:** fill and override declared paths per invocation with ctx.bind ([629ef7d](https://github.com/elct9620/kobako/commit/629ef7dfa35273c952685fefb4c5793b11886aee))
|
|
14
|
+
* **sandbox:** return a frozen Kobako::Invocation from #eval and #run ([e9f4634](https://github.com/elct9620/kobako/commit/e9f463403bea26fad32e52d560b979684f555ed1))
|
|
15
|
+
* **tasks:** gate the wasmtime driver against a magnus dependency ([268c3f6](https://github.com/elct9620/kobako/commit/268c3f65f53d64b465458bca5c0c9b0d0467df44))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **context:** raise ArgumentError for a spent ctx.bind ([2783bba](https://github.com/elct9620/kobako/commit/2783bba20f139554178389d4c860e0c280cab550))
|
|
21
|
+
* **extension:** re-assert dependencies after a failed seal ([72839ba](https://github.com/elct9620/kobako/commit/72839bac6186e81a1d4323bd3f1eb4f10abf3d64))
|
|
22
|
+
|
|
3
23
|
## [0.19.0](https://github.com/elct9620/kobako/compare/v0.18.0...v0.19.0) (2026-07-19)
|
|
4
24
|
|
|
5
25
|
|
data/Cargo.lock
CHANGED
|
@@ -43,9 +43,9 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
45
45
|
name = "anyhow"
|
|
46
|
-
version = "1.0.
|
|
46
|
+
version = "1.0.104"
|
|
47
47
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
-
checksum = "
|
|
48
|
+
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
|
49
49
|
|
|
50
50
|
[[package]]
|
|
51
51
|
name = "arbitrary"
|
|
@@ -929,20 +929,21 @@ dependencies = [
|
|
|
929
929
|
|
|
930
930
|
[[package]]
|
|
931
931
|
name = "kobako"
|
|
932
|
-
version = "0.
|
|
932
|
+
version = "0.20.0"
|
|
933
933
|
dependencies = [
|
|
934
934
|
"kobako-runtime",
|
|
935
935
|
"kobako-wasmtime",
|
|
936
936
|
"magnus",
|
|
937
|
+
"rb-sys",
|
|
937
938
|
]
|
|
938
939
|
|
|
939
940
|
[[package]]
|
|
940
941
|
name = "kobako-runtime"
|
|
941
|
-
version = "0.
|
|
942
|
+
version = "0.12.0"
|
|
942
943
|
|
|
943
944
|
[[package]]
|
|
944
945
|
name = "kobako-wasmtime"
|
|
945
|
-
version = "0.
|
|
946
|
+
version = "0.12.0"
|
|
946
947
|
dependencies = [
|
|
947
948
|
"kobako-runtime",
|
|
948
949
|
"libc",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.11.0...kobako-runtime-v0.12.0) (2026-07-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **kobako-runtime:** Synchronize kobako crates versions
|
|
9
|
+
|
|
3
10
|
## [0.11.0](https://github.com/elct9620/kobako/compare/kobako-runtime-v0.10.2...kobako-runtime-v0.11.0) (2026-07-19)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.11.0...kobako-wasmtime-v0.12.0) (2026-07-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Miscellaneous Chores
|
|
7
|
+
|
|
8
|
+
* **kobako-wasmtime:** Synchronize kobako crates versions
|
|
9
|
+
|
|
3
10
|
## [0.11.0](https://github.com/elct9620/kobako/compare/kobako-wasmtime-v0.10.2...kobako-wasmtime-v0.11.0) (2026-07-19)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
[package]
|
|
17
17
|
name = "kobako-wasmtime"
|
|
18
|
-
version = "0.
|
|
18
|
+
version = "0.12.0"
|
|
19
19
|
edition = "2021"
|
|
20
20
|
description = "wasmtime implementation of the kobako host runtime contract."
|
|
21
21
|
license = "Apache-2.0"
|
|
@@ -28,7 +28,7 @@ categories = ["wasm", "virtualization"]
|
|
|
28
28
|
# The engine-neutral contract this crate implements. The version pin
|
|
29
29
|
# rides the linked release group; the path keeps in-tree builds (and
|
|
30
30
|
# the Ruby gem, which ships both crates) resolving locally.
|
|
31
|
-
kobako-runtime = { version = "0.
|
|
31
|
+
kobako-runtime = { version = "0.12.0", path = "../kobako-runtime" }
|
|
32
32
|
# wasmtime — host-side embedder for kobako.wasm. We disable default-features
|
|
33
33
|
# and opt back in only what kobako needs: a Cranelift-backed runtime that can
|
|
34
34
|
# compile a pre-built wasm32-wasip1 module on the host triple, plus the `wat`
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/ext/kobako/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "kobako"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.20.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Aotokitsuruya <contact@aotoki.me>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -10,7 +10,11 @@ publish = false
|
|
|
10
10
|
crate-type = ["cdylib"]
|
|
11
11
|
|
|
12
12
|
[dependencies]
|
|
13
|
-
magnus = { version = "0.8.2" }
|
|
13
|
+
magnus = { version = "0.8.2", features = ["rb-sys"] }
|
|
14
|
+
# Raw Ruby FFI for the GVL release/re-acquire calls magnus does not wrap
|
|
15
|
+
# (`rb_thread_call_without_gvl` / `rb_thread_call_with_gvl`). Pinned to the
|
|
16
|
+
# rb-sys magnus resolves so both share one binding instance.
|
|
17
|
+
rb-sys = { version = "0.9", default-features = false }
|
|
14
18
|
# The engine-neutral host runtime contract plus its wasmtime driver —
|
|
15
19
|
# the ext is a thin magnus shim over them. Pure path dependencies: both
|
|
16
20
|
# crates ship inside the gem, so installs never resolve them against
|
|
@@ -104,11 +104,12 @@ impl GuestYielder {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/// The Ruby-Proc bridge: a `DispatchHandler` backed by the
|
|
108
|
-
/// dispatch `Proc`
|
|
109
|
-
/// one place the dispatch seam touches `magnus`; the wasm
|
|
110
|
-
/// only the trait. The Proc
|
|
111
|
-
///
|
|
107
|
+
/// The Ruby-Proc bridge: a `DispatchHandler` backed by the per-invocation
|
|
108
|
+
/// dispatch `Proc` handed to `Runtime#eval` / `#run` as a call argument.
|
|
109
|
+
/// This is the one place the dispatch seam touches `magnus`; the wasm
|
|
110
|
+
/// runtime sees only the trait. The Proc stays GC-rooted as a live argument
|
|
111
|
+
/// on the Ruby stack for the synchronous call; this struct holds an
|
|
112
|
+
/// `Opaque` copy of the same handle.
|
|
112
113
|
pub(super) struct RubyDispatchHandler {
|
|
113
114
|
on_dispatch: Opaque<Value>,
|
|
114
115
|
}
|
|
@@ -127,24 +128,29 @@ impl DispatchHandler for RubyDispatchHandler {
|
|
|
127
128
|
/// violation surfaced as `None` — the dispatcher then walks the
|
|
128
129
|
/// 0-return wire-fault path.
|
|
129
130
|
fn dispatch(&self, request: &[u8], yielder: &mut dyn Yielder) -> Option<Vec<u8>> {
|
|
130
|
-
// The
|
|
131
|
-
// the
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
// The guest may be running GVL-free (`gvl: :release`); re-acquire the
|
|
132
|
+
// GVL for the whole callback before touching any Ruby VALUE. In hold
|
|
133
|
+
// mode `reenter` runs the body inline — the GVL is already held.
|
|
134
|
+
super::gvl::reenter(|| {
|
|
135
|
+
// The wasmtime callback runs on the same Ruby thread that called
|
|
136
|
+
// the active Sandbox invocation (#eval or #run) — the invariant
|
|
137
|
+
// SPEC Implementation Standards Architecture pins for the host gem
|
|
138
|
+
// — so `Ruby::get()` is available here once the GVL is held.
|
|
139
|
+
// Panicking with `expect` localises the violation rather than
|
|
140
|
+
// letting a nonsense error propagate.
|
|
141
|
+
let ruby = Ruby::get().expect("Ruby handle unavailable in __kobako_dispatch");
|
|
142
|
+
let proc_value: Value = ruby.get_inner(self.on_dispatch);
|
|
143
|
+
let req_str = ruby.str_from_slice(request);
|
|
144
|
+
// Hand the Proc a frame-scoped yielder object as its second arg and
|
|
145
|
+
// invalidate it the instant the Proc returns, so a guest block that
|
|
146
|
+
// escapes the dispatch frame can never deref the freed stack
|
|
147
|
+
// pointer. `guest_yielder` holds no Ruby Value, so it needs no GC
|
|
148
|
+
// mark — the GC has nothing to trace through it.
|
|
149
|
+
let guest_yielder = ruby.obj_wrap(GuestYielder::new(yielder));
|
|
150
|
+
let resp: Result<RString, magnus::Error> =
|
|
151
|
+
proc_value.funcall("call", (req_str, guest_yielder));
|
|
152
|
+
guest_yielder.invalidate();
|
|
153
|
+
resp.ok().map(super::rstring_to_vec)
|
|
154
|
+
})
|
|
149
155
|
}
|
|
150
156
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//! Releasing Ruby's GVL for the guest-execution span so distinct Sandboxes on
|
|
2
|
+
//! distinct Threads run their guest code in parallel.
|
|
3
|
+
//!
|
|
4
|
+
//! `region` brackets one invocation's guest span, releasing the GVL when the
|
|
5
|
+
//! Sandbox asks for it; `reenter` re-acquires the GVL for a guest→host dispatch
|
|
6
|
+
//! callback, which is ordinary Ruby and must hold the lock. A thread-local flag
|
|
7
|
+
//! stops a nested dispatch from re-acquiring a second time, and both directions
|
|
8
|
+
//! run their closure through a `catch_unwind` trampoline because a panic must
|
|
9
|
+
//! never cross the C frame of `rb_thread_call_without_gvl` /
|
|
10
|
+
//! `rb_thread_call_with_gvl` (undefined behaviour). The flag is per-thread,
|
|
11
|
+
//! matching the one-thread-one-Sandbox contract, and is restored through a drop
|
|
12
|
+
//! guard so an unwinding panic cannot leave it corrupt for the next invocation.
|
|
13
|
+
|
|
14
|
+
use std::cell::Cell;
|
|
15
|
+
use std::os::raw::c_void;
|
|
16
|
+
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
|
|
17
|
+
use std::ptr;
|
|
18
|
+
|
|
19
|
+
thread_local! {
|
|
20
|
+
// True while this thread sits inside a region whose GVL we released and
|
|
21
|
+
// have not yet re-acquired. Drives `reenter`'s one-shot re-acquire.
|
|
22
|
+
static GVL_RELEASED: Cell<bool> = const { Cell::new(false) };
|
|
23
|
+
}
|
|
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
|
+
pub(crate) fn region<F, R>(release: bool, f: F) -> R
|
|
28
|
+
where
|
|
29
|
+
F: FnOnce() -> R,
|
|
30
|
+
{
|
|
31
|
+
if !release {
|
|
32
|
+
return f();
|
|
33
|
+
}
|
|
34
|
+
let _guard = ReleasedGuard::set(true);
|
|
35
|
+
call_via(f, |func, data| unsafe {
|
|
36
|
+
rb_sys::rb_thread_call_without_gvl(Some(func), data, None, ptr::null_mut())
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Run `f` (a guest→host dispatch callback) under the GVL. Re-acquires the GVL
|
|
41
|
+
/// when the enclosing region released it, and runs inline when the GVL is
|
|
42
|
+
/// already held — hold mode, or a nested dispatch whose outer frame re-acquired
|
|
43
|
+
/// it.
|
|
44
|
+
pub(crate) fn reenter<F, R>(f: F) -> R
|
|
45
|
+
where
|
|
46
|
+
F: FnOnce() -> R,
|
|
47
|
+
{
|
|
48
|
+
if !GVL_RELEASED.with(|g| g.get()) {
|
|
49
|
+
return f();
|
|
50
|
+
}
|
|
51
|
+
let _guard = ReleasedGuard::set(false);
|
|
52
|
+
call_via(f, |func, data| unsafe {
|
|
53
|
+
rb_sys::rb_thread_call_with_gvl(Some(func), data)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Restores `GVL_RELEASED` to its prior value on drop, so an unwinding panic
|
|
58
|
+
/// through a released region cannot leave the flag stuck.
|
|
59
|
+
struct ReleasedGuard {
|
|
60
|
+
prev: bool,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl ReleasedGuard {
|
|
64
|
+
fn set(value: bool) -> Self {
|
|
65
|
+
Self {
|
|
66
|
+
prev: GVL_RELEASED.with(|g| g.replace(value)),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl Drop for ReleasedGuard {
|
|
72
|
+
fn drop(&mut self) {
|
|
73
|
+
GVL_RELEASED.with(|g| g.set(self.prev));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
struct GvlCtx<F, R> {
|
|
78
|
+
f: Option<F>,
|
|
79
|
+
result: Option<std::thread::Result<R>>,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// The `extern "C"` shim the `rb_thread_call_*` functions call: it runs the
|
|
83
|
+
/// erased closure under `catch_unwind` so a panic is captured here rather than
|
|
84
|
+
/// unwinding across the C frame, and stashes the outcome for `call_via`.
|
|
85
|
+
unsafe extern "C" fn trampoline<F, R>(data: *mut c_void) -> *mut c_void
|
|
86
|
+
where
|
|
87
|
+
F: FnOnce() -> R,
|
|
88
|
+
{
|
|
89
|
+
let ctx = unsafe { &mut *data.cast::<GvlCtx<F, R>>() };
|
|
90
|
+
let f = ctx.f.take().expect("gvl trampoline runs its closure once");
|
|
91
|
+
ctx.result = Some(catch_unwind(AssertUnwindSafe(f)));
|
|
92
|
+
ptr::null_mut()
|
|
93
|
+
}
|
|
94
|
+
|
|
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
|
+
fn call_via<F, R>(
|
|
99
|
+
f: F,
|
|
100
|
+
invoke: impl FnOnce(unsafe extern "C" fn(*mut c_void) -> *mut c_void, *mut c_void) -> *mut c_void,
|
|
101
|
+
) -> R
|
|
102
|
+
where
|
|
103
|
+
F: FnOnce() -> R,
|
|
104
|
+
{
|
|
105
|
+
let mut ctx = GvlCtx::<F, R> {
|
|
106
|
+
f: Some(f),
|
|
107
|
+
result: None,
|
|
108
|
+
};
|
|
109
|
+
invoke(trampoline::<F, R>, ptr::addr_of_mut!(ctx).cast::<c_void>());
|
|
110
|
+
match ctx.result.take().expect("gvl trampoline stores a result") {
|
|
111
|
+
Ok(value) => value,
|
|
112
|
+
Err(payload) => resume_unwind(payload),
|
|
113
|
+
}
|
|
114
|
+
}
|