kobako 0.12.1 → 0.12.2
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 +11 -0
- data/Cargo.lock +15 -2
- data/Cargo.toml +6 -2
- data/README.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +8 -0
- data/crates/kobako-runtime/Cargo.toml +23 -0
- data/crates/kobako-runtime/README.md +34 -0
- data/crates/kobako-runtime/src/dispatch.rs +22 -0
- data/crates/kobako-runtime/src/error.rs +64 -0
- data/crates/kobako-runtime/src/lib.rs +16 -0
- data/crates/kobako-runtime/src/runtime.rs +50 -0
- data/crates/kobako-runtime/src/snapshot.rs +46 -0
- data/crates/kobako-runtime/src/yielder.rs +22 -0
- data/crates/kobako-wasmtime/CHANGELOG.md +8 -0
- data/crates/kobako-wasmtime/Cargo.toml +62 -0
- data/crates/kobako-wasmtime/README.md +32 -0
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/ambient.rs +3 -3
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/cache.rs +30 -41
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/capture.rs +2 -2
- data/crates/kobako-wasmtime/src/config.rs +25 -0
- data/crates/kobako-wasmtime/src/dispatch.rs +110 -0
- data/crates/kobako-wasmtime/src/driver.rs +285 -0
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/exports.rs +5 -6
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/frames.rs +70 -82
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/guest_mem.rs +38 -15
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/instance_pre.rs +13 -21
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/invocation.rs +54 -49
- data/crates/kobako-wasmtime/src/lib.rs +47 -0
- data/{ext/kobako/src/runtime → crates/kobako-wasmtime/src}/trap.rs +29 -35
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +9 -32
- data/ext/kobako/src/runtime/bridge.rs +150 -0
- data/ext/kobako/src/runtime/errors.rs +45 -13
- data/ext/kobako/src/runtime.rs +156 -406
- data/ext/kobako/src/snapshot.rs +27 -62
- data/lib/kobako/catalog/handles.rb +3 -3
- data/lib/kobako/catalog/namespaces.rb +4 -0
- data/lib/kobako/catalog/snippets.rb +4 -0
- data/lib/kobako/codec/encoder.rb +5 -1
- data/lib/kobako/codec/factory.rb +41 -13
- data/lib/kobako/codec/handle_walk.rb +4 -0
- data/lib/kobako/errors.rb +18 -16
- data/lib/kobako/sandbox.rb +20 -18
- data/lib/kobako/sandbox_options.rb +25 -9
- data/lib/kobako/snapshot.rb +7 -13
- data/lib/kobako/transport/dispatcher.rb +2 -2
- data/lib/kobako/transport/response.rb +14 -14
- data/lib/kobako/transport/run.rb +2 -6
- data/lib/kobako/transport/yield.rb +1 -1
- data/lib/kobako/transport/yielder.rb +2 -2
- data/lib/kobako/version.rb +1 -1
- data/release-please-config.json +48 -3
- data/sig/kobako/codec/factory.rbs +3 -0
- data/sig/kobako/errors.rbs +7 -14
- data/sig/kobako/runtime.rbs +8 -3
- data/sig/kobako/sandbox.rbs +2 -2
- data/sig/kobako/sandbox_options.rbs +4 -2
- data/sig/kobako/snapshot.rbs +0 -3
- data/sig/kobako/transport/dispatcher.rbs +1 -1
- data/sig/kobako/transport/run.rbs +2 -2
- data/sig/kobako/transport/yielder.rbs +2 -2
- data/sig/kobako/transport.rbs +8 -0
- metadata +27 -12
- data/ext/kobako/src/runtime/config.rs +0 -25
- data/ext/kobako/src/runtime/dispatch.rs +0 -211
data/release-please-config.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
|
3
3
|
"release-type": "ruby",
|
|
4
|
-
"last-release-sha": "
|
|
4
|
+
"last-release-sha": "98509af508988f708bf0d7a76a718bb0428a177e",
|
|
5
5
|
"packages": {
|
|
6
6
|
".": {
|
|
7
7
|
"component": "kobako",
|
|
@@ -103,13 +103,58 @@
|
|
|
103
103
|
"path": "/wasm/kobako-baker/README.md"
|
|
104
104
|
}
|
|
105
105
|
]
|
|
106
|
+
},
|
|
107
|
+
"crates/kobako-runtime": {
|
|
108
|
+
"component": "kobako-runtime",
|
|
109
|
+
"release-type": "rust",
|
|
110
|
+
"extra-files": [
|
|
111
|
+
{
|
|
112
|
+
"type": "toml",
|
|
113
|
+
"path": "/crates/Cargo.lock",
|
|
114
|
+
"jsonpath": "$.package[?(@.name=='kobako-runtime')].version"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"type": "toml",
|
|
118
|
+
"path": "/Cargo.lock",
|
|
119
|
+
"jsonpath": "$.package[?(@.name=='kobako-runtime')].version"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"type": "generic",
|
|
123
|
+
"path": "/crates/kobako-runtime/README.md"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
"crates/kobako-wasmtime": {
|
|
128
|
+
"component": "kobako-wasmtime",
|
|
129
|
+
"release-type": "rust",
|
|
130
|
+
"extra-files": [
|
|
131
|
+
{
|
|
132
|
+
"type": "toml",
|
|
133
|
+
"path": "/crates/Cargo.lock",
|
|
134
|
+
"jsonpath": "$.package[?(@.name=='kobako-wasmtime')].version"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"type": "toml",
|
|
138
|
+
"path": "/Cargo.lock",
|
|
139
|
+
"jsonpath": "$.package[?(@.name=='kobako-wasmtime')].version"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"type": "toml",
|
|
143
|
+
"path": "/crates/kobako-wasmtime/Cargo.toml",
|
|
144
|
+
"jsonpath": "$.dependencies['kobako-runtime'].version"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"type": "generic",
|
|
148
|
+
"path": "/crates/kobako-wasmtime/README.md"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
106
151
|
}
|
|
107
152
|
},
|
|
108
153
|
"plugins": [
|
|
109
154
|
{
|
|
110
155
|
"type": "linked-versions",
|
|
111
|
-
"groupName": "kobako
|
|
112
|
-
"components": ["kobako-core", "kobako-rs", "kobako-io", "kobako-json", "kobako-regexp", "kobako-baker"]
|
|
156
|
+
"groupName": "kobako crates",
|
|
157
|
+
"components": ["kobako-core", "kobako-rs", "kobako-io", "kobako-json", "kobako-regexp", "kobako-baker", "kobako-runtime", "kobako-wasmtime"]
|
|
113
158
|
}
|
|
114
159
|
],
|
|
115
160
|
"extra-files": [
|
|
@@ -8,6 +8,8 @@ module Kobako
|
|
|
8
8
|
EXT_SYMBOL: Integer
|
|
9
9
|
EXT_HANDLE: Integer
|
|
10
10
|
EXT_ERRENV: Integer
|
|
11
|
+
MAX_EXT_DEPTH: Integer
|
|
12
|
+
EXT_DEPTH_KEY: Symbol
|
|
11
13
|
|
|
12
14
|
def dump: (untyped value) -> String
|
|
13
15
|
def load: (String bytes) -> untyped
|
|
@@ -26,6 +28,7 @@ module Kobako
|
|
|
26
28
|
def unpack_handle: (String payload) -> Kobako::Handle
|
|
27
29
|
def pack_fault: (Kobako::Fault fault) -> String
|
|
28
30
|
def unpack_fault: (String payload) -> Kobako::Fault
|
|
31
|
+
def within_ext_frame: [T] (singleton(Kobako::Codec::Error) over_limit) { () -> T } -> T
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
end
|
data/sig/kobako/errors.rbs
CHANGED
|
@@ -17,7 +17,7 @@ module Kobako
|
|
|
17
17
|
class ModuleNotBuiltError < SetupError
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
module StructuredError : Error
|
|
21
21
|
attr_reader origin: String?
|
|
22
22
|
attr_reader klass: String?
|
|
23
23
|
attr_reader backtrace_lines: Array[String]?
|
|
@@ -32,22 +32,15 @@ module Kobako
|
|
|
32
32
|
) -> void
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
class
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
attr_reader backtrace_lines: Array[String]?
|
|
39
|
-
attr_reader details: untyped
|
|
35
|
+
class SandboxError < Error
|
|
36
|
+
include StructuredError
|
|
37
|
+
end
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
?origin: String?,
|
|
44
|
-
?klass: String?,
|
|
45
|
-
?backtrace_lines: Array[String]?,
|
|
46
|
-
?details: untyped
|
|
47
|
-
) -> void
|
|
39
|
+
class ServiceError < Error
|
|
40
|
+
include StructuredError
|
|
48
41
|
end
|
|
49
42
|
|
|
50
|
-
class
|
|
43
|
+
class HandleExhaustedError < SandboxError
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
class BytecodeError < SandboxError
|
data/sig/kobako/runtime.rbs
CHANGED
|
@@ -10,14 +10,19 @@ module Kobako
|
|
|
10
10
|
Integer? stderr_limit_bytes
|
|
11
11
|
) -> Runtime
|
|
12
12
|
|
|
13
|
-
def on_dispatch=: (^(String) -> String dispatch_proc) -> void
|
|
14
|
-
|
|
15
|
-
def yield_to_active_invocation: (String args_bytes) -> String
|
|
13
|
+
def on_dispatch=: (^(String, Kobako::Transport::_GuestYielder) -> String dispatch_proc) -> void
|
|
16
14
|
|
|
17
15
|
def eval: (String preamble, String source, String snippets) -> Kobako::Snapshot
|
|
18
16
|
|
|
19
17
|
def run: (String preamble, String snippets, String envelope) -> Kobako::Snapshot
|
|
20
18
|
|
|
21
19
|
def usage: () -> [Float, Integer]
|
|
20
|
+
|
|
21
|
+
# Frame-scoped guest re-entry handle the ext hands the dispatch Proc as
|
|
22
|
+
# its second argument; stands in as the +String -> String+ callable the
|
|
23
|
+
# host +Transport::Yielder+ invokes for a block yield.
|
|
24
|
+
class GuestYielder
|
|
25
|
+
def call: (String args_bytes) -> String
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
end
|
data/sig/kobako/sandbox.rbs
CHANGED
|
@@ -8,8 +8,8 @@ module Kobako
|
|
|
8
8
|
# Forwarded to @options via Forwardable#def_delegators.
|
|
9
9
|
def timeout: () -> Float?
|
|
10
10
|
def memory_limit: () -> Integer?
|
|
11
|
-
def stdout_limit: () -> Integer
|
|
12
|
-
def stderr_limit: () -> Integer
|
|
11
|
+
def stdout_limit: () -> Integer?
|
|
12
|
+
def stderr_limit: () -> Integer?
|
|
13
13
|
|
|
14
14
|
def initialize: (
|
|
15
15
|
?wasm_path: String?,
|
|
@@ -6,8 +6,8 @@ module Kobako
|
|
|
6
6
|
|
|
7
7
|
attr_reader timeout: Float?
|
|
8
8
|
attr_reader memory_limit: Integer?
|
|
9
|
-
attr_reader stdout_limit: Integer
|
|
10
|
-
attr_reader stderr_limit: Integer
|
|
9
|
+
attr_reader stdout_limit: Integer?
|
|
10
|
+
attr_reader stderr_limit: Integer?
|
|
11
11
|
|
|
12
12
|
def self.new: (
|
|
13
13
|
?timeout: (Float | Integer)?,
|
|
@@ -28,5 +28,7 @@ module Kobako
|
|
|
28
28
|
def normalize_timeout: ((Float | Integer)? timeout) -> Float?
|
|
29
29
|
|
|
30
30
|
def normalize_memory_limit: (Integer? memory_limit) -> Integer?
|
|
31
|
+
|
|
32
|
+
def normalize_output_limit: (Integer? limit, String name) -> Integer?
|
|
31
33
|
end
|
|
32
34
|
end
|
data/sig/kobako/snapshot.rbs
CHANGED
|
@@ -5,11 +5,8 @@ module Kobako
|
|
|
5
5
|
def stdout_truncated: () -> bool
|
|
6
6
|
def stderr_bytes: () -> String
|
|
7
7
|
def stderr_truncated: () -> bool
|
|
8
|
-
def wall_time: () -> Float
|
|
9
|
-
def memory_peak: () -> Integer
|
|
10
8
|
|
|
11
9
|
def stdout: () -> Kobako::Capture
|
|
12
10
|
def stderr: () -> Kobako::Capture
|
|
13
|
-
def usage: () -> Kobako::Usage
|
|
14
11
|
end
|
|
15
12
|
end
|
|
@@ -12,7 +12,7 @@ module Kobako
|
|
|
12
12
|
|
|
13
13
|
CALLABLE_ALLOW: Array[Symbol]
|
|
14
14
|
|
|
15
|
-
def self?.dispatch: (String request_bytes, Kobako::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler,
|
|
15
|
+
def self?.dispatch: (String request_bytes, Kobako::Catalog::Namespaces namespaces, Kobako::Catalog::Handles handler, Kobako::Transport::_GuestYielder yield_to_guest) -> String
|
|
16
16
|
|
|
17
17
|
def self?.resolve_call_args: (Kobako::Transport::Request request, Kobako::Catalog::Handles handler) -> [Array[untyped], Hash[Symbol, untyped]]
|
|
18
18
|
|
|
@@ -17,9 +17,9 @@ module Kobako
|
|
|
17
17
|
|
|
18
18
|
def normalize_entrypoint: (Symbol | String target) -> Symbol
|
|
19
19
|
|
|
20
|
-
def validate_args!: (Array[untyped] args) ->
|
|
20
|
+
def validate_args!: (Array[untyped] args) -> void
|
|
21
21
|
|
|
22
|
-
def validate_kwargs!: (Hash[untyped, untyped] kwargs) ->
|
|
22
|
+
def validate_kwargs!: (Hash[untyped, untyped] kwargs) -> void
|
|
23
23
|
|
|
24
24
|
def forged_handle_message: (String slot) -> String
|
|
25
25
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module Kobako
|
|
2
2
|
module Transport
|
|
3
3
|
class Yielder
|
|
4
|
-
@yield_to_guest:
|
|
4
|
+
@yield_to_guest: Kobako::Transport::_GuestYielder
|
|
5
5
|
@break_tag: Symbol
|
|
6
6
|
@handler: Kobako::Catalog::Handles
|
|
7
7
|
@active: bool
|
|
8
8
|
|
|
9
|
-
def initialize: (
|
|
9
|
+
def initialize: (Kobako::Transport::_GuestYielder yield_to_guest, Symbol break_tag, Kobako::Catalog::Handles handler) -> void
|
|
10
10
|
|
|
11
11
|
def yield: (*untyped args) -> untyped
|
|
12
12
|
|
data/sig/kobako/transport.rbs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
module Kobako
|
|
2
2
|
module Transport
|
|
3
|
+
# The guest re-entry callable a dispatch hands to a Yielder: invoked
|
|
4
|
+
# with the encoded yield args, it returns the encoded YieldResponse.
|
|
5
|
+
# The ext's Kobako::Runtime::GuestYielder is the production conformer;
|
|
6
|
+
# modelled structurally so the Transport layer needs no upward
|
|
7
|
+
# dependency on Runtime.
|
|
8
|
+
interface _GuestYielder
|
|
9
|
+
def call: (String) -> String
|
|
10
|
+
end
|
|
3
11
|
end
|
|
4
12
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kobako
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aotokitsuruya
|
|
@@ -54,23 +54,38 @@ files:
|
|
|
54
54
|
- LICENSE
|
|
55
55
|
- README.md
|
|
56
56
|
- SECURITY.md
|
|
57
|
+
- crates/kobako-runtime/CHANGELOG.md
|
|
58
|
+
- crates/kobako-runtime/Cargo.toml
|
|
59
|
+
- crates/kobako-runtime/README.md
|
|
60
|
+
- crates/kobako-runtime/src/dispatch.rs
|
|
61
|
+
- crates/kobako-runtime/src/error.rs
|
|
62
|
+
- crates/kobako-runtime/src/lib.rs
|
|
63
|
+
- crates/kobako-runtime/src/runtime.rs
|
|
64
|
+
- crates/kobako-runtime/src/snapshot.rs
|
|
65
|
+
- crates/kobako-runtime/src/yielder.rs
|
|
66
|
+
- crates/kobako-wasmtime/CHANGELOG.md
|
|
67
|
+
- crates/kobako-wasmtime/Cargo.toml
|
|
68
|
+
- crates/kobako-wasmtime/README.md
|
|
69
|
+
- crates/kobako-wasmtime/src/ambient.rs
|
|
70
|
+
- crates/kobako-wasmtime/src/cache.rs
|
|
71
|
+
- crates/kobako-wasmtime/src/capture.rs
|
|
72
|
+
- crates/kobako-wasmtime/src/config.rs
|
|
73
|
+
- crates/kobako-wasmtime/src/dispatch.rs
|
|
74
|
+
- crates/kobako-wasmtime/src/driver.rs
|
|
75
|
+
- crates/kobako-wasmtime/src/exports.rs
|
|
76
|
+
- crates/kobako-wasmtime/src/frames.rs
|
|
77
|
+
- crates/kobako-wasmtime/src/guest_mem.rs
|
|
78
|
+
- crates/kobako-wasmtime/src/instance_pre.rs
|
|
79
|
+
- crates/kobako-wasmtime/src/invocation.rs
|
|
80
|
+
- crates/kobako-wasmtime/src/lib.rs
|
|
81
|
+
- crates/kobako-wasmtime/src/trap.rs
|
|
57
82
|
- data/kobako.wasm
|
|
58
83
|
- ext/kobako/Cargo.toml
|
|
59
84
|
- ext/kobako/extconf.rb
|
|
60
85
|
- ext/kobako/src/lib.rs
|
|
61
86
|
- ext/kobako/src/runtime.rs
|
|
62
|
-
- ext/kobako/src/runtime/
|
|
63
|
-
- ext/kobako/src/runtime/cache.rs
|
|
64
|
-
- ext/kobako/src/runtime/capture.rs
|
|
65
|
-
- ext/kobako/src/runtime/config.rs
|
|
66
|
-
- ext/kobako/src/runtime/dispatch.rs
|
|
87
|
+
- ext/kobako/src/runtime/bridge.rs
|
|
67
88
|
- ext/kobako/src/runtime/errors.rs
|
|
68
|
-
- ext/kobako/src/runtime/exports.rs
|
|
69
|
-
- ext/kobako/src/runtime/frames.rs
|
|
70
|
-
- ext/kobako/src/runtime/guest_mem.rs
|
|
71
|
-
- ext/kobako/src/runtime/instance_pre.rs
|
|
72
|
-
- ext/kobako/src/runtime/invocation.rs
|
|
73
|
-
- ext/kobako/src/runtime/trap.rs
|
|
74
89
|
- ext/kobako/src/snapshot.rs
|
|
75
90
|
- lib/kobako.rb
|
|
76
91
|
- lib/kobako/capture.rb
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
//! Per-`Runtime` execution configuration.
|
|
2
|
-
//!
|
|
3
|
-
//! The wall-clock and per-channel capture caps a `Kobako::Sandbox`
|
|
4
|
-
//! forwards into `Runtime::from_path`. A plain value carrier owned by the
|
|
5
|
-
//! `Runtime` — distinct from the process-wide engine/module `super::cache`
|
|
6
|
-
//! (which is shared across every Sandbox) and from the per-invocation
|
|
7
|
-
//! `super::invocation::Invocation` (which the wasm engine mutates from
|
|
8
|
-
//! inside a run). These caps are read only by `Runtime` methods between
|
|
9
|
-
//! runs, so they live here.
|
|
10
|
-
|
|
11
|
-
use std::time::Duration;
|
|
12
|
-
|
|
13
|
-
/// Wall-clock and output caps for one `Runtime`. `None` on any field
|
|
14
|
-
/// disables that cap.
|
|
15
|
-
pub(crate) struct Config {
|
|
16
|
-
/// Wall-clock cap for one guest `#eval` / `#run`. Stamped into a
|
|
17
|
-
/// per-run `Instant` deadline by `Runtime::prime_caps`.
|
|
18
|
-
pub(crate) timeout: Option<Duration>,
|
|
19
|
-
/// Byte cap for guest stdout capture.
|
|
20
|
-
/// Sizes the per-run `MemoryOutputPipe` and computes the truncation
|
|
21
|
-
/// flag in `Runtime::build_snapshot`.
|
|
22
|
-
pub(crate) stdout_limit_bytes: Option<usize>,
|
|
23
|
-
/// Byte cap for guest stderr capture. Mirror of `stdout_limit_bytes`.
|
|
24
|
-
pub(crate) stderr_limit_bytes: Option<usize>,
|
|
25
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
//! Host-side dispatch for the `__kobako_dispatch` import.
|
|
2
|
-
//!
|
|
3
|
-
//! When the guest invokes the wasm import declared in
|
|
4
|
-
//! `wasm/kobako-core/src/abi.rs`, wasmtime calls back into the host
|
|
5
|
-
//! through the closure registered by `instance_pre::build_linker`.
|
|
6
|
-
//! That closure delegates here. The dispatcher:
|
|
7
|
-
//!
|
|
8
|
-
//! 1. Reads the Request bytes from guest linear memory.
|
|
9
|
-
//! 2. Invokes the Ruby-side dispatch Proc bound via
|
|
10
|
-
//! `Runtime#on_dispatch=` and recovers Response bytes.
|
|
11
|
-
//! 3. Allocates a guest buffer via `__kobako_alloc(len)` invoked
|
|
12
|
-
//! through `Caller::get_export`.
|
|
13
|
-
//! 4. Writes the Response bytes into the guest buffer.
|
|
14
|
-
//! 5. Returns packed `(ptr<<32)|len` for the guest to decode.
|
|
15
|
-
//!
|
|
16
|
-
//! Returns 0 on any step failure. `Kobako::Sandbox#initialize` always
|
|
17
|
-
//! installs the dispatch Proc before any invocation, so reaching the
|
|
18
|
-
//! dispatcher with no Proc bound is itself a wire-layer fault; the
|
|
19
|
-
//! guest maps a 0 return to a trap. Failures during normal dispatch
|
|
20
|
-
//! surface as Response.err envelopes from
|
|
21
|
-
//! `Kobako::Transport::Dispatcher.dispatch` itself — they never reach
|
|
22
|
-
//! this 0-return path.
|
|
23
|
-
//!
|
|
24
|
-
//! ## Why this module writes to `stderr`
|
|
25
|
-
//!
|
|
26
|
-
//! This file is the one place in `ext/` that deliberately prints
|
|
27
|
-
//! through `eprintln!`. The host normally surfaces faults by
|
|
28
|
-
//! raising a `MagnusError` back into Ruby; the dispatcher contract
|
|
29
|
-
//! is the exception — it must return a packed `i64` to the guest
|
|
30
|
-
//! and cannot raise, so a 0 return is the only signal the wasm side
|
|
31
|
-
//! receives. The guest collapses every 0 into the same trap, so the
|
|
32
|
-
//! Ruby host has no way to attribute the failure to a specific step
|
|
33
|
-
//! (missing `memory` export vs. no dispatch Proc bound vs. the Proc
|
|
34
|
-
//! raised vs. `__kobako_alloc` returned 0 vs. `memory.write`
|
|
35
|
-
//! rejected).
|
|
36
|
-
//!
|
|
37
|
-
//! `handle` writes a single `[kobako-dispatch] <reason>` line to
|
|
38
|
-
//! `stderr` on each failure path so operators have a breadcrumb to
|
|
39
|
-
//! correlate the trap with the actual cause. The line is emitted in
|
|
40
|
-
//! both debug and release builds on purpose: dispatcher failures are
|
|
41
|
-
//! wire-layer faults rather than expected error paths (`Kobako::Sandbox`
|
|
42
|
-
//! always installs the Proc, the Proc is contracted never to raise,
|
|
43
|
-
//! etc.), so the "release-build noise" cost is bounded — under normal
|
|
44
|
-
//! operation the line is never written. Operators that need to silence
|
|
45
|
-
//! the stream can redirect the host process's stderr, but the kobako
|
|
46
|
-
//! convention is "ext never logs" plus this single, named exception.
|
|
47
|
-
|
|
48
|
-
use core::cell::Cell;
|
|
49
|
-
use core::ptr::NonNull;
|
|
50
|
-
|
|
51
|
-
use magnus::value::{Opaque, ReprValue};
|
|
52
|
-
use magnus::{Error as MagnusError, RString, Ruby, Value};
|
|
53
|
-
use wasmtime::Caller;
|
|
54
|
-
|
|
55
|
-
use super::invocation::Invocation;
|
|
56
|
-
|
|
57
|
-
// ============================================================
|
|
58
|
-
// Active-caller pointer for the per-thread Invocation slot
|
|
59
|
-
// (SPEC.md Single-Invocation Slot).
|
|
60
|
-
// ============================================================
|
|
61
|
-
//
|
|
62
|
-
// `Runtime#yield_to_active_invocation` (whose body is the
|
|
63
|
-
// `__kobako_yield_to_block` guest export) runs synchronously inside a
|
|
64
|
-
// Ruby Service callback that itself was invoked from inside this
|
|
65
|
-
// dispatcher — at that moment we are several stack frames deep in
|
|
66
|
-
// `try_handle`, with the original `&mut Caller<'_, Invocation>` parked
|
|
67
|
-
// unused on the Rust stack while Ruby code is running. The yield path
|
|
68
|
-
// needs the same Caller to call the guest export, but the Rust borrow
|
|
69
|
-
// type is non-`'static` so it cannot be stored on the `Invocation`
|
|
70
|
-
// struct directly (the `&mut Caller` outlives no struct field — its
|
|
71
|
-
// lifetime ends when `handle` returns to wasmtime).
|
|
72
|
-
//
|
|
73
|
-
// The pointer is therefore erased to `NonNull<()>` and parked in a
|
|
74
|
-
// per-thread slot — the materialised form of the SPEC.md
|
|
75
|
-
// "Single-Invocation Slot" invariant. The single-threaded wasm
|
|
76
|
-
// execution per Sandbox plus the LIFO re-entry shape of nested
|
|
77
|
-
// dispatch frames ensures no aliasing across threads or across
|
|
78
|
-
// frames; the recovery invariant lives at `current_caller`. The
|
|
79
|
-
// pointer is set on entry to `handle` and restored to the outer
|
|
80
|
-
// frame's value on every exit through a drop guard.
|
|
81
|
-
|
|
82
|
-
thread_local! {
|
|
83
|
-
static ACTIVE_CALLER: Cell<Option<NonNull<()>>> = const { Cell::new(None) };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/// RAII guard that saves the previous `ACTIVE_CALLER` value on
|
|
87
|
-
/// installation and restores it on drop. Nested `__kobako_dispatch`
|
|
88
|
-
/// frames stack within one Invocation — the inner frame's `set`
|
|
89
|
-
/// swaps in its own pointer while remembering the outer's; drop
|
|
90
|
-
/// restores the outer so its continuation (e.g. iterating over another
|
|
91
|
-
/// guest block) still finds a live caller.
|
|
92
|
-
pub(crate) struct CallerGuard {
|
|
93
|
-
previous: Option<NonNull<()>>,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
impl CallerGuard {
|
|
97
|
-
fn set(ptr: NonNull<()>) -> Self {
|
|
98
|
-
let previous = ACTIVE_CALLER.with(|c| c.replace(Some(ptr)));
|
|
99
|
-
Self { previous }
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
impl Drop for CallerGuard {
|
|
104
|
-
fn drop(&mut self) {
|
|
105
|
-
ACTIVE_CALLER.with(|c| c.set(self.previous));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/// Recover the active `&mut Caller<'_, Invocation>` set by the
|
|
110
|
-
/// enclosing `handle` frame. Returns `None` when no dispatch frame is
|
|
111
|
-
/// active on this thread.
|
|
112
|
-
///
|
|
113
|
-
/// # Safety
|
|
114
|
-
///
|
|
115
|
-
/// The returned reference aliases the original `&mut Caller` borrow
|
|
116
|
-
/// held on the Rust stack inside `handle`'s enclosing frame. The
|
|
117
|
-
/// original borrow is logically inactive while Ruby code is running
|
|
118
|
-
/// (it is parked on the stack between `invoke_on_dispatch` and the
|
|
119
|
-
/// eventual `funcall` return), and the SPEC.md Single-Invocation Slot
|
|
120
|
-
/// invariant (one Invocation per OS thread for the duration of any
|
|
121
|
-
/// invocation) guarantees no other Rust frame can observe it. Callers
|
|
122
|
-
/// must not retain the returned `&mut` past the synchronous Ruby
|
|
123
|
-
/// callback that requested it — i.e. only use it inside one short
|
|
124
|
-
/// magnus method body and let the borrow end before the method returns.
|
|
125
|
-
pub(crate) fn current_caller<'a>() -> Option<&'a mut Caller<'a, Invocation>> {
|
|
126
|
-
let raw: NonNull<()> = ACTIVE_CALLER.with(|c| c.get())?;
|
|
127
|
-
// SAFETY: see item doc.
|
|
128
|
-
Some(unsafe { &mut *raw.as_ptr().cast::<Caller<'a, Invocation>>() })
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/// Drive a single `__kobako_dispatch` invocation end-to-end. Entry point
|
|
132
|
-
/// from the wasmtime closure registered by `instance_pre::build_linker`.
|
|
133
|
-
///
|
|
134
|
-
/// Returns the packed `(ptr<<32)|len` u64 on success, 0 on any
|
|
135
|
-
/// wire-layer fault. Failure paths log a `[kobako-dispatch]` line to
|
|
136
|
-
/// `stderr` so operators have a breadcrumb when the guest sees a 0
|
|
137
|
-
/// return and traps. The bound dispatch Proc is contracted never to
|
|
138
|
-
/// raise (it folds Service exceptions into Response.err envelopes),
|
|
139
|
-
/// so reaching the failure path is always a wiring bug or wire-layer
|
|
140
|
-
/// fault rather than an expected path.
|
|
141
|
-
pub(crate) fn handle(caller: &mut Caller<'_, Invocation>, req_ptr: i32, req_len: i32) -> i64 {
|
|
142
|
-
// SAFETY: lifetime erased to `NonNull<()>` per the module's
|
|
143
|
-
// Invocation-slot doc. The pointer is restored by `_caller_guard`
|
|
144
|
-
// before this function returns, and only
|
|
145
|
-
// `Runtime#yield_to_active_invocation` (running inside a Ruby
|
|
146
|
-
// callback we are about to invoke) reads it through `current_caller`.
|
|
147
|
-
let ptr: NonNull<()> = NonNull::from(&mut *caller).cast();
|
|
148
|
-
let _caller_guard = CallerGuard::set(ptr);
|
|
149
|
-
|
|
150
|
-
match try_handle(caller, req_ptr, req_len) {
|
|
151
|
-
Ok(packed) => packed,
|
|
152
|
-
Err(reason) => {
|
|
153
|
-
eprintln!("[kobako-dispatch] {}", reason);
|
|
154
|
-
0
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/// Result-returning core of `handle`. Pulled out so each early
|
|
160
|
-
/// failure path carries a diagnostic string instead of an opaque 0.
|
|
161
|
-
fn try_handle(
|
|
162
|
-
caller: &mut Caller<'_, Invocation>,
|
|
163
|
-
req_ptr: i32,
|
|
164
|
-
req_len: i32,
|
|
165
|
-
) -> Result<i64, &'static str> {
|
|
166
|
-
let req_bytes = super::guest_mem::read(caller, req_ptr, req_len)?;
|
|
167
|
-
|
|
168
|
-
// `Kobako::Sandbox` always installs the dispatch Proc before
|
|
169
|
-
// invoking the runtime, so reaching this branch indicates a misuse
|
|
170
|
-
// rather than a normal control path.
|
|
171
|
-
let on_dispatch = caller
|
|
172
|
-
.data()
|
|
173
|
-
.on_dispatch()
|
|
174
|
-
.ok_or("a Sandbox callback fired outside an active Sandbox#run — please report this as a kobako bug")?;
|
|
175
|
-
|
|
176
|
-
let resp_bytes = invoke_on_dispatch(on_dispatch, &req_bytes).map_err(|_| {
|
|
177
|
-
"a Sandbox callback raised an exception instead of returning a fault — please report this as a kobako bug"
|
|
178
|
-
})?;
|
|
179
|
-
|
|
180
|
-
write_response(caller, &resp_bytes)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/// Invoke the Ruby-side dispatch `Proc` with the request bytes and return
|
|
184
|
-
/// the encoded Response bytes. The Proc is contracted to fold every
|
|
185
|
-
/// dispatch failure into a `Response.err` envelope (see
|
|
186
|
-
/// `Kobako::Transport::Dispatcher.dispatch`), so reaching the error
|
|
187
|
-
/// branch is itself a wire-layer fault rather than a normal control path.
|
|
188
|
-
fn invoke_on_dispatch(
|
|
189
|
-
on_dispatch: Opaque<Value>,
|
|
190
|
-
req_bytes: &[u8],
|
|
191
|
-
) -> Result<Vec<u8>, MagnusError> {
|
|
192
|
-
// The wasmtime callback runs on the same Ruby thread that called the
|
|
193
|
-
// active Sandbox invocation (#eval or #run) — the invariant SPEC
|
|
194
|
-
// Implementation Standards Architecture pins for the host gem — so
|
|
195
|
-
// `Ruby::get()` is always available here. Panicking with `expect`
|
|
196
|
-
// localises the violation rather than letting a nonsense error
|
|
197
|
-
// propagate.
|
|
198
|
-
let ruby = Ruby::get().expect("Ruby handle unavailable in __kobako_dispatch");
|
|
199
|
-
let proc_value: Value = ruby.get_inner(on_dispatch);
|
|
200
|
-
let req_str = ruby.str_from_slice(req_bytes);
|
|
201
|
-
let resp: RString = proc_value.funcall("call", (req_str,))?;
|
|
202
|
-
Ok(super::rstring_to_vec(resp))
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/// Allocate a guest-side buffer and copy the response bytes into it via
|
|
206
|
-
/// `super::guest_mem::alloc_and_write`, returning the packed
|
|
207
|
-
/// `(ptr<<32)|len` u64 the guest's `__kobako_dispatch` import expects.
|
|
208
|
-
fn write_response(caller: &mut Caller<'_, Invocation>, bytes: &[u8]) -> Result<i64, &'static str> {
|
|
209
|
-
let ptr = super::guest_mem::alloc_and_write(caller, bytes)?;
|
|
210
|
-
Ok(((ptr as i64) << 32) | (bytes.len() as i64))
|
|
211
|
-
}
|