kobako 0.10.0-aarch64-linux → 0.11.0-aarch64-linux
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 +7 -0
- data/README.md +14 -7
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/capture.rb +5 -7
- data/lib/kobako/catalog/handles.rb +24 -31
- data/lib/kobako/catalog/namespaces.rb +19 -27
- data/lib/kobako/catalog/snippets.rb +10 -16
- data/lib/kobako/codec/utils.rb +6 -9
- data/lib/kobako/errors.rb +33 -39
- data/lib/kobako/handle.rb +2 -3
- data/lib/kobako/namespace.rb +8 -11
- data/lib/kobako/outcome.rb +12 -14
- data/lib/kobako/pool.rb +18 -24
- data/lib/kobako/sandbox.rb +61 -83
- data/lib/kobako/sandbox_options.rb +5 -9
- data/lib/kobako/snapshot.rb +2 -4
- data/lib/kobako/snippet/binary.rb +1 -3
- data/lib/kobako/snippet/source.rb +1 -2
- data/lib/kobako/snippet.rb +1 -2
- data/lib/kobako/transport/dispatcher.rb +39 -38
- data/lib/kobako/transport/request.rb +1 -1
- data/lib/kobako/transport/run.rb +23 -28
- data/lib/kobako/transport/yielder.rb +11 -17
- data/lib/kobako/transport.rb +2 -3
- data/lib/kobako/usage.rb +10 -13
- data/lib/kobako/version.rb +1 -1
- data/sig/kobako/transport/dispatcher.rbs +2 -0
- metadata +2 -2
data/lib/kobako/namespace.rb
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
|
-
# A named grouping of Members for one Sandbox
|
|
5
|
-
# ({docs/behavior.md B-07..B-11}[link:../../docs/behavior.md]).
|
|
4
|
+
# A named grouping of Members for one Sandbox.
|
|
6
5
|
# Returned by +Sandbox#define+. Each instance owns a flat name→object
|
|
7
6
|
# table of Members; member binding is validated against {NAME_PATTERN}.
|
|
8
7
|
class Namespace
|
|
9
|
-
# Ruby constant-name pattern shared by Namespace and Member names
|
|
10
|
-
# ({docs/behavior.md B-07/B-08 Notes}[link:../../docs/behavior.md]).
|
|
8
|
+
# Ruby constant-name pattern shared by Namespace and Member names.
|
|
11
9
|
NAME_PATTERN = /\A[A-Z]\w*\z/
|
|
12
10
|
|
|
13
11
|
attr_reader :name
|
|
@@ -26,9 +24,8 @@ module Kobako
|
|
|
26
24
|
# object that responds to the methods guest code will invoke. Returns
|
|
27
25
|
# +self+ for chaining. Raises +ArgumentError+ when +member+ does not
|
|
28
26
|
# match the constant pattern, when a Member of the same name is
|
|
29
|
-
# already bound
|
|
30
|
-
#
|
|
31
|
-
# registration ({docs/behavior.md E-45}[link:../../docs/behavior.md]).
|
|
27
|
+
# already bound, or when the owning Sandbox's first invocation has
|
|
28
|
+
# sealed Service registration.
|
|
32
29
|
def bind(member, object)
|
|
33
30
|
raise ArgumentError, "cannot bind after first Sandbox invocation" if @sealed
|
|
34
31
|
|
|
@@ -39,10 +36,10 @@ module Kobako
|
|
|
39
36
|
self
|
|
40
37
|
end
|
|
41
38
|
|
|
42
|
-
# Mark this Namespace as sealed
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
39
|
+
# Mark this Namespace as sealed. Called by
|
|
40
|
+
# +Kobako::Catalog::Namespaces#seal!+ on the owning Sandbox's first
|
|
41
|
+
# invocation; afterwards {#bind} raises +ArgumentError+. Idempotent;
|
|
42
|
+
# returns +self+.
|
|
46
43
|
def seal!
|
|
47
44
|
@sealed = true
|
|
48
45
|
self
|
data/lib/kobako/outcome.rb
CHANGED
|
@@ -7,8 +7,7 @@ module Kobako
|
|
|
7
7
|
# Host-facing boundary for the OUTCOME_BUFFER produced by
|
|
8
8
|
# +__kobako_eval+. Takes raw outcome bytes — a one-byte tag followed by
|
|
9
9
|
# the msgpack-encoded body — and maps them to either the unwrapped
|
|
10
|
-
# mruby return value or a raised three-layer
|
|
11
|
-
# ({docs/behavior.md Error Scenarios}[link:../../docs/behavior.md]) exception.
|
|
10
|
+
# mruby return value or a raised three-layer exception.
|
|
12
11
|
#
|
|
13
12
|
# Self-contained: this module owns the wire framing (tag bytes,
|
|
14
13
|
# body decoding), and the +Panic+ wire record lives at
|
|
@@ -17,11 +16,11 @@ module Kobako
|
|
|
17
16
|
# nothing in +Transport+ participates.
|
|
18
17
|
#
|
|
19
18
|
# * tag 0x01, decode OK → return decoded value
|
|
20
|
-
# * tag 0x01, decode fails → SandboxError
|
|
21
|
-
# * tag 0x02, origin="service" → ServiceError
|
|
22
|
-
# * tag 0x02, origin="sandbox"/missing → SandboxError
|
|
23
|
-
# * tag 0x02, decode fails → SandboxError
|
|
24
|
-
# * unknown tag → TrapError
|
|
19
|
+
# * tag 0x01, decode fails → SandboxError
|
|
20
|
+
# * tag 0x02, origin="service" → ServiceError
|
|
21
|
+
# * tag 0x02, origin="sandbox"/missing → SandboxError
|
|
22
|
+
# * tag 0x02, decode fails → SandboxError
|
|
23
|
+
# * unknown tag → TrapError
|
|
25
24
|
module Outcome
|
|
26
25
|
# First byte of the OUTCOME_BUFFER for the success branch — body is
|
|
27
26
|
# the bare msgpack encoding of the returned value
|
|
@@ -71,7 +70,7 @@ module Kobako
|
|
|
71
70
|
[tag, body]
|
|
72
71
|
end
|
|
73
72
|
|
|
74
|
-
# Decode failure on the success tag is a SandboxError
|
|
73
|
+
# Decode failure on the success tag is a SandboxError: the
|
|
75
74
|
# framing was fine, but the carried value is unrepresentable. The
|
|
76
75
|
# specific codec fault is stashed in +details+ rather
|
|
77
76
|
# than spliced into the message — callers cannot act on the inner
|
|
@@ -86,7 +85,7 @@ module Kobako
|
|
|
86
85
|
)
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
# Decode failure on the panic tag is a SandboxError
|
|
88
|
+
# Decode failure on the panic tag is a SandboxError. Either
|
|
90
89
|
# path raises — on success the decoded Panic is mapped to its three-
|
|
91
90
|
# layer exception via +build_panic_error+ and raised; on wire-decode
|
|
92
91
|
# failure the rescue path raises the wire-violation +SandboxError+.
|
|
@@ -130,13 +129,12 @@ module Kobako
|
|
|
130
129
|
)
|
|
131
130
|
end
|
|
132
131
|
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# callers can rescue specific failure paths. +origin="service"+ →
|
|
132
|
+
# Map the panic +class+ field to the matching Ruby exception subclass
|
|
133
|
+
# so callers can rescue specific failure paths. +origin="service"+ →
|
|
136
134
|
# +ServiceError+; +origin="sandbox"+ plus
|
|
137
135
|
# +class="Kobako::BytecodeError"+ selects the +BytecodeError+
|
|
138
|
-
# subclass
|
|
139
|
-
#
|
|
136
|
+
# subclass. Everything else falls back to the base class for the
|
|
137
|
+
# origin.
|
|
140
138
|
def panic_target_class(panic)
|
|
141
139
|
case panic.origin
|
|
142
140
|
when Panic::ORIGIN_SERVICE
|
data/lib/kobako/pool.rb
CHANGED
|
@@ -5,32 +5,28 @@ require_relative "sandbox"
|
|
|
5
5
|
|
|
6
6
|
module Kobako
|
|
7
7
|
# Kobako::Pool — a bounded set of warm, identically set-up Sandboxes
|
|
8
|
-
# handed out one exclusive holder at a time
|
|
9
|
-
# ({docs/behavior.md B-46..B-48}[link:../../docs/behavior.md]).
|
|
8
|
+
# handed out one exclusive holder at a time.
|
|
10
9
|
#
|
|
11
10
|
# Construction forwards every +Kobako::Sandbox.new+ keyword verbatim
|
|
12
11
|
# and holds the optional block as the per-Sandbox setup hook; a
|
|
13
12
|
# checkout prefers an idle Sandbox and constructs a new one only when
|
|
14
|
-
# none is idle and fewer than +slots+ exist
|
|
15
|
-
# to +checkout_timeout+ seconds when every slot is held
|
|
16
|
-
# the +TrapError+ discard-and-recreate contract at checkin
|
|
13
|
+
# none is idle and fewer than +slots+ exist. +#with+ blocks up
|
|
14
|
+
# to +checkout_timeout+ seconds when every slot is held, applies
|
|
15
|
+
# the +TrapError+ discard-and-recreate contract at checkin, and
|
|
17
16
|
# the Pool releases everything with its own reachability — there is no
|
|
18
|
-
# teardown verb
|
|
17
|
+
# teardown verb.
|
|
19
18
|
class Pool
|
|
20
|
-
# The +#with+ wait bound applied when +checkout_timeout+ is not given
|
|
21
|
-
# ({docs/behavior.md B-46}[link:../../docs/behavior.md]).
|
|
19
|
+
# The +#with+ wait bound applied when +checkout_timeout+ is not given.
|
|
22
20
|
DEFAULT_CHECKOUT_TIMEOUT_SECONDS = 5.0
|
|
23
21
|
|
|
24
|
-
# Build a Pool of up to +slots+ Sandboxes
|
|
25
|
-
# ({docs/behavior.md B-46}[link:../../docs/behavior.md]). +slots+ is
|
|
22
|
+
# Build a Pool of up to +slots+ Sandboxes. +slots+ is
|
|
26
23
|
# a positive Integer; +checkout_timeout+ bounds the +#with+ wait in
|
|
27
24
|
# seconds (+nil+ waits indefinitely); every other keyword is
|
|
28
25
|
# forwarded verbatim to +Kobako::Sandbox.new+. The optional block
|
|
29
26
|
# runs exactly once per constructed Sandbox — it is the setup window
|
|
30
27
|
# for +#define+ / +#preload+ before that Sandbox's first checkout.
|
|
31
28
|
# No Sandbox is constructed here. Raises +ArgumentError+ for an
|
|
32
|
-
# invalid +slots+ / +checkout_timeout
|
|
33
|
-
# ({docs/behavior.md E-47}[link:../../docs/behavior.md]).
|
|
29
|
+
# invalid +slots+ / +checkout_timeout+.
|
|
34
30
|
def initialize(slots:, checkout_timeout: DEFAULT_CHECKOUT_TIMEOUT_SECONDS, **sandbox_options, &setup)
|
|
35
31
|
validate_slots!(slots)
|
|
36
32
|
@slots = slots
|
|
@@ -44,11 +40,9 @@ module Kobako
|
|
|
44
40
|
end
|
|
45
41
|
|
|
46
42
|
# Yield one exclusively-held Sandbox to the block and return the
|
|
47
|
-
# block's value
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# ({docs/behavior.md E-46}[link:../../docs/behavior.md]). The Sandbox
|
|
51
|
-
# returns to the pool at block exit — unless the block raised
|
|
43
|
+
# block's value. Blocks while every slot is held; raises
|
|
44
|
+
# +Kobako::PoolTimeoutError+ once the wait exceeds +checkout_timeout+.
|
|
45
|
+
# The Sandbox returns to the pool at block exit — unless the block raised
|
|
52
46
|
# +Kobako::TrapError+, in which case the unrecoverable Sandbox is
|
|
53
47
|
# discarded and its slot refills by a fresh construction on next
|
|
54
48
|
# demand.
|
|
@@ -68,12 +62,12 @@ module Kobako
|
|
|
68
62
|
private
|
|
69
63
|
|
|
70
64
|
# Acquire a Sandbox and hand it over in pre-invocation state — empty
|
|
71
|
-
# output buffers and truncation predicates false
|
|
65
|
+
# output buffers and truncation predicates false.
|
|
72
66
|
def checkout
|
|
73
67
|
acquire.tap(&:reset_invocation_state!)
|
|
74
68
|
end
|
|
75
69
|
|
|
76
|
-
# The idle-first claim loop
|
|
70
|
+
# The idle-first claim loop: an idle Sandbox wins, unclaimed
|
|
77
71
|
# capacity constructs, and a full pool waits for a checkin.
|
|
78
72
|
def acquire
|
|
79
73
|
timeout = @checkout_timeout
|
|
@@ -105,7 +99,7 @@ module Kobako
|
|
|
105
99
|
end
|
|
106
100
|
|
|
107
101
|
# Wait for a checkin or freed capacity; raises
|
|
108
|
-
# +Kobako::PoolTimeoutError+ once +deadline+ has passed
|
|
102
|
+
# +Kobako::PoolTimeoutError+ once +deadline+ has passed. Must
|
|
109
103
|
# run while holding +@mutex+.
|
|
110
104
|
def await_slot!(deadline)
|
|
111
105
|
remaining = deadline && (deadline - monotonic_now)
|
|
@@ -119,7 +113,7 @@ module Kobako
|
|
|
119
113
|
|
|
120
114
|
# Construct and set up one pooled Sandbox against the capacity
|
|
121
115
|
# reserved by +claim_or_wait+. Construction and setup-block errors
|
|
122
|
-
# propagate to the checkout caller unchanged
|
|
116
|
+
# propagate to the checkout caller unchanged; the reserved
|
|
123
117
|
# capacity is released so a later checkout can retry.
|
|
124
118
|
def construct_slot
|
|
125
119
|
done = false
|
|
@@ -154,7 +148,7 @@ module Kobako
|
|
|
154
148
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
155
149
|
end
|
|
156
150
|
|
|
157
|
-
#
|
|
151
|
+
# Pre-flight for +slots+ — no coercion, a positive Integer is
|
|
158
152
|
# the only accepted shape.
|
|
159
153
|
def validate_slots!(slots)
|
|
160
154
|
return if slots.is_a?(Integer) && slots.positive?
|
|
@@ -163,8 +157,8 @@ module Kobako
|
|
|
163
157
|
end
|
|
164
158
|
|
|
165
159
|
# Coerce +checkout_timeout+ into the Float seconds the wait loop
|
|
166
|
-
# consumes, or +nil+ to wait indefinitely — the
|
|
167
|
-
# idiom
|
|
160
|
+
# consumes, or +nil+ to wait indefinitely — the same normalisation
|
|
161
|
+
# idiom +SandboxOptions+ applies to +timeout+.
|
|
168
162
|
def normalize_checkout_timeout(checkout_timeout)
|
|
169
163
|
return nil if checkout_timeout.nil?
|
|
170
164
|
unless checkout_timeout.is_a?(Numeric)
|
data/lib/kobako/sandbox.rb
CHANGED
|
@@ -13,21 +13,19 @@ require_relative "catalog"
|
|
|
13
13
|
|
|
14
14
|
module Kobako
|
|
15
15
|
# Kobako::Sandbox — the user-facing entry point for executing guest mruby
|
|
16
|
-
# scripts inside a wasmtime-hosted Wasm module
|
|
17
|
-
# ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
|
|
16
|
+
# scripts inside a wasmtime-hosted Wasm module.
|
|
18
17
|
#
|
|
19
18
|
# The Sandbox owns the +Kobako::Runtime+, the per-Sandbox
|
|
20
|
-
# +Kobako::Catalog::Handles
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# Sandboxes amortises both costs automatically.
|
|
19
|
+
# +Kobako::Catalog::Handles+, the per-instance
|
|
20
|
+
# +Kobako::Catalog::Namespaces+ (which receives the +Catalog::Handles+ by
|
|
21
|
+
# injection so guest→host dispatch and host→guest auto-wrap share one
|
|
22
|
+
# allocator), and the dispatch +Proc+ / +yield_to_guest+ lambda installed
|
|
23
|
+
# on the Runtime via +Runtime#on_dispatch=+. The underlying wasmtime Engine
|
|
24
|
+
# and compiled Module are cached at process scope by the native ext and
|
|
25
|
+
# never surface to Ruby — constructing many Sandboxes amortises both costs
|
|
26
|
+
# automatically.
|
|
29
27
|
#
|
|
30
|
-
# Output capture policy
|
|
28
|
+
# Output capture policy: the
|
|
31
29
|
# per-channel cap (+stdout_limit+ / +stderr_limit+) is enforced inside the
|
|
32
30
|
# WASI pipe — the host buffer stops growing at the cap, subsequent guest
|
|
33
31
|
# writes on that channel fail or are dropped, and +#run+ still returns
|
|
@@ -46,9 +44,8 @@ module Kobako
|
|
|
46
44
|
|
|
47
45
|
# Returns the bytes the guest wrote to stdout during the most recent
|
|
48
46
|
# invocation as a UTF-8 String, clipped at +stdout_limit+. Empty before
|
|
49
|
-
# any invocation
|
|
50
|
-
#
|
|
51
|
-
# observe overflow.
|
|
47
|
+
# any invocation; the byte content never contains a truncation sentinel,
|
|
48
|
+
# so use +#stdout_truncated?+ to observe overflow.
|
|
52
49
|
def stdout
|
|
53
50
|
@stdout_capture.bytes
|
|
54
51
|
end
|
|
@@ -61,9 +58,8 @@ module Kobako
|
|
|
61
58
|
end
|
|
62
59
|
|
|
63
60
|
# Returns +true+ iff stdout capture during the most recent invocation
|
|
64
|
-
# exceeded +stdout_limit+
|
|
65
|
-
#
|
|
66
|
-
# ({docs/behavior.md B-03}[link:../../docs/behavior.md]).
|
|
61
|
+
# exceeded +stdout_limit+. Resets to +false+ at the start of the next
|
|
62
|
+
# invocation.
|
|
67
63
|
def stdout_truncated?
|
|
68
64
|
@stdout_capture.truncated?
|
|
69
65
|
end
|
|
@@ -75,8 +71,7 @@ module Kobako
|
|
|
75
71
|
end
|
|
76
72
|
|
|
77
73
|
# Returns the +Kobako::Usage+ value object for the most recent
|
|
78
|
-
# invocation (
|
|
79
|
-
# Carries +wall_time+ (Float seconds the guest export call spent
|
|
74
|
+
# invocation. Carries +wall_time+ (Float seconds the guest export call spent
|
|
80
75
|
# inside wasmtime) and +memory_peak+ (Integer bytes, high-water of
|
|
81
76
|
# the per-invocation +memory.grow+ delta past the entry-time
|
|
82
77
|
# baseline). Returns +Kobako::Usage::EMPTY+ before any invocation;
|
|
@@ -109,9 +104,8 @@ module Kobako
|
|
|
109
104
|
reset_invocation_state!
|
|
110
105
|
end
|
|
111
106
|
|
|
112
|
-
# Declare or retrieve the Namespace named +name+ on this Sandbox
|
|
113
|
-
#
|
|
114
|
-
# Symbol or String in constant form. Returns the
|
|
107
|
+
# Declare or retrieve the Namespace named +name+ on this Sandbox. +name+
|
|
108
|
+
# must be a Symbol or String in constant form. Returns the
|
|
115
109
|
# +Kobako::Namespace+.
|
|
116
110
|
#
|
|
117
111
|
# Raises +ArgumentError+ when called after the first invocation, or
|
|
@@ -120,20 +114,18 @@ module Kobako
|
|
|
120
114
|
@services.define(name)
|
|
121
115
|
end
|
|
122
116
|
|
|
123
|
-
# Register a snippet on this Sandbox in one of two forms
|
|
124
|
-
# ({docs/behavior.md B-32}[link:../../docs/behavior.md]):
|
|
117
|
+
# Register a snippet on this Sandbox in one of two forms:
|
|
125
118
|
#
|
|
126
119
|
# * +preload(code: source, name: Name)+ — +source+ is mruby source
|
|
127
120
|
# as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. The +name+
|
|
128
121
|
# becomes the snippet's +(snippet:Name)+ backtrace filename and
|
|
129
|
-
# is the dedupe key
|
|
122
|
+
# is the dedupe key that rejects a duplicate +code:+ snippet.
|
|
130
123
|
# * +preload(binary: bytes)+ — +bytes+ is precompiled RITE
|
|
131
124
|
# bytecode as a +String+. The canonical name, when present,
|
|
132
125
|
# lives in the bytecode's embedded +debug_info+ and is resolved
|
|
133
126
|
# by the guest at load time; the host treats the bytes as
|
|
134
|
-
# opaque. Structural failures
|
|
135
|
-
#
|
|
136
|
-
# surface as +Kobako::BytecodeError+ on the first invocation.
|
|
127
|
+
# opaque. Structural failures surface as +Kobako::BytecodeError+
|
|
128
|
+
# on the first invocation.
|
|
137
129
|
#
|
|
138
130
|
# Subsequent invocations (+#eval+ or +#run+) replay every registered
|
|
139
131
|
# snippet — in insertion order — against the fresh +mrb_state+
|
|
@@ -145,11 +137,9 @@ module Kobako
|
|
|
145
137
|
# supplied, when both forms are mixed (e.g., +code:+ and +binary:+
|
|
146
138
|
# together, or +binary:+ paired with +name:+), when +code+ / +bytes+
|
|
147
139
|
# is not a +String+, when +name+ does not match the constant
|
|
148
|
-
# pattern
|
|
149
|
-
# when
|
|
150
|
-
#
|
|
151
|
-
# called after the first invocation
|
|
152
|
-
# ({docs/behavior.md E-35, B-33}[link:../../docs/behavior.md]).
|
|
140
|
+
# pattern, when +name+ duplicates an already-registered +code:+ form
|
|
141
|
+
# snippet, or when called after the first invocation has sealed the
|
|
142
|
+
# snippet table.
|
|
153
143
|
def preload(code: nil, name: nil, binary: nil)
|
|
154
144
|
raise ArgumentError, "cannot preload after first Sandbox invocation" if @services.sealed?
|
|
155
145
|
|
|
@@ -157,17 +147,16 @@ module Kobako
|
|
|
157
147
|
self
|
|
158
148
|
end
|
|
159
149
|
|
|
160
|
-
# Dispatch into a preloaded entrypoint constant
|
|
161
|
-
# ({docs/behavior.md B-31}[link:../../docs/behavior.md]). Delegates host
|
|
150
|
+
# Dispatch into a preloaded entrypoint constant. Delegates host
|
|
162
151
|
# pre-flight and wire encoding to +Kobako::Transport::Run+ /
|
|
163
152
|
# +Kobako::Transport::Run#encode+: a non-Symbol/String +target+ raises
|
|
164
|
-
# +TypeError
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
153
|
+
# +TypeError+, while a +target+ failing the constant pattern, a forged
|
|
154
|
+
# +Kobako::Handle+ in +args+ / +kwargs+, or a non-Symbol +kwargs+ key
|
|
155
|
+
# raise +ArgumentError+. The guest resolves +target+ as a top-level
|
|
156
|
+
# constant, calls +#call+ on it with +args+ / +kwargs+, and returns the
|
|
157
|
+
# deserialized result. The first invocation seals the Service registry
|
|
158
|
+
# and snippet table. Runtime errors follow the same three-class
|
|
159
|
+
# taxonomy as +#eval+.
|
|
171
160
|
def run(target, *args, **kwargs)
|
|
172
161
|
run_envelope = Transport::Run.new(entrypoint: target, args: args, kwargs: kwargs)
|
|
173
162
|
invoke!(:run) do
|
|
@@ -175,29 +164,27 @@ module Kobako
|
|
|
175
164
|
end
|
|
176
165
|
end
|
|
177
166
|
|
|
178
|
-
# Execute a guest mruby source string in a fresh +mrb_state+
|
|
179
|
-
#
|
|
180
|
-
# mruby source as a UTF-8 String. Returns the deserialized last
|
|
167
|
+
# Execute a guest mruby source string in a fresh +mrb_state+. +code+ is
|
|
168
|
+
# the mruby source as a UTF-8 String. Returns the deserialized last
|
|
181
169
|
# expression of the source.
|
|
182
170
|
#
|
|
183
171
|
# Source delivery uses the WASI stdin three-frame protocol
|
|
184
172
|
# ({docs/wire-codec.md Invocation channels}[link:../../docs/wire-codec.md]):
|
|
185
173
|
# Frame 1 carries the msgpack-encoded preamble (Namespace / Member
|
|
186
174
|
# registry snapshot), Frame 2 carries the user source UTF-8 bytes, and
|
|
187
|
-
# Frame 3 carries the snippet table registered via +#preload
|
|
175
|
+
# Frame 3 carries the snippet table registered via +#preload+.
|
|
188
176
|
# Each frame is prefixed by a 4-byte big-endian u32 length; Frame 3 is
|
|
189
177
|
# mandatory-presence — an empty snippet table sends an empty msgpack
|
|
190
178
|
# array, never an absent frame.
|
|
191
179
|
#
|
|
192
|
-
# The first invocation seals the Service registry and snippet table
|
|
193
|
-
#
|
|
194
|
-
# +#define+ / +#preload+ calls raise +ArgumentError+.
|
|
180
|
+
# The first invocation seals the Service registry and snippet table;
|
|
181
|
+
# subsequent +#define+ / +#preload+ calls raise +ArgumentError+.
|
|
195
182
|
#
|
|
196
183
|
# Raises +Kobako::TrapError+ on a Wasm trap or wire-violation fallback;
|
|
197
184
|
# +Kobako::SandboxError+ when the guest ran to completion but failed
|
|
198
185
|
# (including when +code+ is +nil+ or not a String, or when a preloaded
|
|
199
|
-
# snippet's replay raises
|
|
200
|
-
#
|
|
186
|
+
# snippet's replay raises); +Kobako::ServiceError+ on an unrescued
|
|
187
|
+
# Service capability failure.
|
|
201
188
|
def eval(code)
|
|
202
189
|
raise SandboxError, "code must be a String, got #{code.class}" unless code.is_a?(String)
|
|
203
190
|
|
|
@@ -207,15 +194,11 @@ module Kobako
|
|
|
207
194
|
end
|
|
208
195
|
|
|
209
196
|
# Reset all per-invocation observable state to its pre-invocation
|
|
210
|
-
# sentinels — both per-channel captures
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
# (between-invocation reset) so both paths agree on what
|
|
216
|
-
# "pre-invocation state" means; +Kobako::Pool+ calls it at checkout
|
|
217
|
-
# so a pooled Sandbox hands over empty output buffers
|
|
218
|
-
# ({docs/behavior.md B-47}[link:../../docs/behavior.md]).
|
|
197
|
+
# sentinels — both per-channel captures and the per-last-invocation
|
|
198
|
+
# usage record. Shared by +#initialize+ (first-time setup) and
|
|
199
|
+
# +#begin_invocation!+ (between-invocation reset) so both paths agree on
|
|
200
|
+
# what "pre-invocation state" means; +Kobako::Pool+ calls it at checkout
|
|
201
|
+
# so a pooled Sandbox hands over empty output buffers.
|
|
219
202
|
def reset_invocation_state!
|
|
220
203
|
@stdout_capture = Capture::EMPTY
|
|
221
204
|
@stderr_capture = Capture::EMPTY
|
|
@@ -224,14 +207,13 @@ module Kobako
|
|
|
224
207
|
|
|
225
208
|
private
|
|
226
209
|
|
|
227
|
-
# Configure the +Runtime+'s host↔guest dispatch wiring
|
|
228
|
-
# ({docs/behavior.md B-12}[link:../../docs/behavior.md]). Builds a
|
|
210
|
+
# Configure the +Runtime+'s host↔guest dispatch wiring. Builds a
|
|
229
211
|
# lambda that re-enters the guest via
|
|
230
|
-
# +Runtime#yield_to_active_invocation+
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
212
|
+
# +Runtime#yield_to_active_invocation+ and a dispatch +Proc+ that routes
|
|
213
|
+
# guest→host calls through the stateless +Transport::Dispatcher+,
|
|
214
|
+
# capturing +@services+ / +@handler+ in the closure. Both are registered
|
|
215
|
+
# on the +Runtime+ once at construction time so the wasm ext callback can
|
|
216
|
+
# fire without further setup.
|
|
235
217
|
def install_dispatch_proc!
|
|
236
218
|
yield_to_guest = ->(args_bytes) { @runtime.yield_to_active_invocation(args_bytes) }
|
|
237
219
|
@runtime.on_dispatch = lambda do |request_bytes|
|
|
@@ -239,14 +221,13 @@ module Kobako
|
|
|
239
221
|
end
|
|
240
222
|
end
|
|
241
223
|
|
|
242
|
-
# Per-invocation prologue
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
# capability state — capture buffers, truncation predicates, and the
|
|
224
|
+
# Per-invocation prologue. Seals the Service / snippet registries on
|
|
225
|
+
# first call (idempotent) and zeros the per-invocation capability
|
|
226
|
+
# state — capture buffers, truncation predicates, and the
|
|
246
227
|
# +Catalog::Handles+ counter — before the guest runs. The
|
|
247
|
-
# +Catalog::Handles+ itself is held as +@handler+ and never exposed
|
|
248
|
-
# this class: SPEC.md Terminology pins it as "Not exposed to the
|
|
249
|
-
# Host App"
|
|
228
|
+
# +Catalog::Handles+ itself is held as +@handler+ and never exposed
|
|
229
|
+
# beyond this class: SPEC.md Terminology pins it as "Not exposed to the
|
|
230
|
+
# Host App".
|
|
250
231
|
def begin_invocation!
|
|
251
232
|
@services.seal!
|
|
252
233
|
@handler.reset!
|
|
@@ -254,8 +235,7 @@ module Kobako
|
|
|
254
235
|
end
|
|
255
236
|
|
|
256
237
|
# Read the per-last-invocation +wall_time+ and +memory_peak+ from
|
|
257
|
-
# the ext and wrap them as a +Kobako::Usage+ value object
|
|
258
|
-
# ({docs/behavior.md B-35}[link:../../docs/behavior.md]). Runs in
|
|
238
|
+
# the ext and wrap them as a +Kobako::Usage+ value object. Runs in
|
|
259
239
|
# the +invoke!+ +ensure+ block so the usage record is populated on
|
|
260
240
|
# every outcome — value return, +Kobako::TrapError+ (including
|
|
261
241
|
# +TimeoutError+ / +MemoryLimitError+), +Kobako::SandboxError+,
|
|
@@ -273,8 +253,7 @@ module Kobako
|
|
|
273
253
|
end
|
|
274
254
|
|
|
275
255
|
# Pick the +TrapError+ subclass to re-raise based on +err+'s actual
|
|
276
|
-
# class. Cap-trap subclasses
|
|
277
|
-
# ({docs/behavior.md E-19 / E-20}[link:../../docs/behavior.md])
|
|
256
|
+
# class. Cap-trap subclasses (+TimeoutError+ / +MemoryLimitError+)
|
|
278
257
|
# preserve their named identity; everything else collapses to the
|
|
279
258
|
# base +Kobako::TrapError+. The ext already raises the right subclass
|
|
280
259
|
# directly, so this is a pure re-attribution that lets +#invoke!+
|
|
@@ -298,9 +277,8 @@ module Kobako
|
|
|
298
277
|
# +Capture+ and feeds +#return_bytes+ to +Outcome.decode+; usage is
|
|
299
278
|
# populated by the +ensure+ readout ({#read_usage!}) on every outcome.
|
|
300
279
|
# The rescue chain is the single trap-translation boundary —
|
|
301
|
-
# configured-cap paths
|
|
302
|
-
# (
|
|
303
|
-
# surface as named TrapError subclasses; everything else surfaces as
|
|
280
|
+
# configured-cap paths surface as named TrapError subclasses
|
|
281
|
+
# (+TimeoutError+ / +MemoryLimitError+); everything else surfaces as
|
|
304
282
|
# the base +TrapError+.
|
|
305
283
|
def invoke!(verb)
|
|
306
284
|
begin_invocation!
|
|
@@ -309,7 +287,7 @@ module Kobako
|
|
|
309
287
|
@stderr_capture = snapshot.stderr
|
|
310
288
|
# A Capability Handle in the result is decoded as a Kobako::Handle
|
|
311
289
|
# token; restore it to the host object the guest referenced before
|
|
312
|
-
# handing the value to the Host App
|
|
290
|
+
# handing the value to the Host App. @handler still holds this
|
|
313
291
|
# invocation's table — reset only happens at the next #begin_invocation!.
|
|
314
292
|
Codec::Utils.deep_restore(Outcome.decode(snapshot.return_bytes), @handler)
|
|
315
293
|
rescue Kobako::TrapError => e
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Kobako
|
|
4
4
|
# Kobako::SandboxOptions — immutable Value Object holding the four
|
|
5
|
-
# per-Sandbox configuration caps
|
|
6
|
-
# E-20}[link:../../docs/behavior.md]). Built on the +class X <
|
|
5
|
+
# per-Sandbox configuration caps. Built on the +class X <
|
|
7
6
|
# Data.define(...)+ subclass form (the Steep-friendly shape — see
|
|
8
7
|
# +lib/kobako/outcome/panic.rb+).
|
|
9
8
|
#
|
|
@@ -13,18 +12,15 @@ module Kobako
|
|
|
13
12
|
# +super+. Anything that survives +SandboxOptions.new+ is a wire-ready
|
|
14
13
|
# cap bundle the +Kobako::Runtime+ constructor consumes as-is.
|
|
15
14
|
class SandboxOptions < Data.define(:timeout, :memory_limit, :stdout_limit, :stderr_limit)
|
|
16
|
-
# Default wall-clock timeout for a single invocation: 60 seconds
|
|
17
|
-
# ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
|
|
15
|
+
# Default wall-clock timeout for a single invocation: 60 seconds.
|
|
18
16
|
DEFAULT_TIMEOUT_SECONDS = 60.0
|
|
19
17
|
|
|
20
18
|
# Default cap on the per-invocation guest linear-memory delta:
|
|
21
|
-
# 1 MiB
|
|
22
|
-
#
|
|
23
|
-
# watermark sit outside this budget — see B-01 Notes.
|
|
19
|
+
# 1 MiB. The mruby image's initial allocation and prior invocations'
|
|
20
|
+
# watermark sit outside this budget.
|
|
24
21
|
DEFAULT_MEMORY_LIMIT = 1 << 20
|
|
25
22
|
|
|
26
|
-
# Default per-channel capture ceiling: 1 MiB
|
|
27
|
-
# ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
|
|
23
|
+
# Default per-channel capture ceiling: 1 MiB.
|
|
28
24
|
DEFAULT_OUTPUT_LIMIT = 1 << 20
|
|
29
25
|
|
|
30
26
|
def initialize(timeout: DEFAULT_TIMEOUT_SECONDS,
|
data/lib/kobako/snapshot.rb
CHANGED
|
@@ -16,8 +16,7 @@ module Kobako
|
|
|
16
16
|
# Host App.
|
|
17
17
|
class Snapshot
|
|
18
18
|
# Wrap the stdout capture pair (+stdout_bytes+, +stdout_truncated+)
|
|
19
|
-
# as a +Kobako::Capture+ value object.
|
|
20
|
-
# B-04}[link:../../docs/behavior.md] — the byte content never carries
|
|
19
|
+
# as a +Kobako::Capture+ value object. The byte content never carries
|
|
21
20
|
# a truncation sentinel; +#truncated?+ is the only way to observe
|
|
22
21
|
# that the cap was hit.
|
|
23
22
|
def stdout
|
|
@@ -31,8 +30,7 @@ module Kobako
|
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
# Wrap the per-last-invocation usage pair (+wall_time+,
|
|
34
|
-
# +memory_peak+) as a +Kobako::Usage+ value object
|
|
35
|
-
# ({docs/behavior.md B-35}[link:../../docs/behavior.md]).
|
|
33
|
+
# +memory_peak+) as a +Kobako::Usage+ value object.
|
|
36
34
|
def usage
|
|
37
35
|
Usage.new(wall_time: wall_time, memory_peak: memory_peak)
|
|
38
36
|
end
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
module Kobako
|
|
4
4
|
module Snippet
|
|
5
5
|
# Kobako::Snippet::Binary — value object representing a single
|
|
6
|
-
# +#preload(binary:)+ entry held by +Kobako::Catalog::Snippets
|
|
7
|
-
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
6
|
+
# +#preload(binary:)+ entry held by +Kobako::Catalog::Snippets+.
|
|
8
7
|
#
|
|
9
8
|
# The +body+ is RITE bytecode (as emitted by +mrbc+) carried as an
|
|
10
9
|
# +ASCII_8BIT+ String so msgpack-ruby encodes it as +bin+ family on
|
|
@@ -12,7 +11,6 @@ module Kobako
|
|
|
12
11
|
# The host treats the bytes as opaque — the snippet's canonical
|
|
13
12
|
# name, when present, lives in the bytecode's embedded +debug_info+
|
|
14
13
|
# and is resolved by the guest at load time; structural validation
|
|
15
|
-
# ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
|
|
16
14
|
# is deferred to the first invocation's guest replay.
|
|
17
15
|
#
|
|
18
16
|
# The class is a +Data.define+ subclass — frozen and value-equal.
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
module Kobako
|
|
4
4
|
module Snippet
|
|
5
5
|
# Kobako::Snippet::Source — value object representing a single
|
|
6
|
-
# +#preload(code:, name:)+ entry held by +Kobako::Catalog::Snippets
|
|
7
|
-
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
6
|
+
# +#preload(code:, name:)+ entry held by +Kobako::Catalog::Snippets+.
|
|
8
7
|
#
|
|
9
8
|
# +name+ is the canonical +Symbol+ identity baked into the loaded
|
|
10
9
|
# IREP's +debug_info+; backtrace frames originating in this snippet
|
data/lib/kobako/snippet.rb
CHANGED
|
@@ -5,8 +5,7 @@ require_relative "snippet/source"
|
|
|
5
5
|
|
|
6
6
|
module Kobako
|
|
7
7
|
# Kobako::Snippet — value-object family for preloaded snippet entries
|
|
8
|
-
# held by +Kobako::Catalog::Snippets
|
|
9
|
-
# ({docs/behavior.md B-32 / B-33}[link:../../docs/behavior.md]).
|
|
8
|
+
# held by +Kobako::Catalog::Snippets+.
|
|
10
9
|
#
|
|
11
10
|
# +Source+ represents a single +#preload(code:, name:)+ entry; +Binary+
|
|
12
11
|
# represents a single +#preload(binary:)+ entry. Both are plain value
|