kobako 0.2.1 → 0.4.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/Cargo.lock +1 -1
- data/README.md +205 -59
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +2 -2
- data/ext/kobako/src/wasm/cache.rs +15 -7
- data/ext/kobako/src/wasm/dispatch.rs +88 -36
- data/ext/kobako/src/wasm/host_state.rs +298 -55
- data/ext/kobako/src/wasm/instance.rs +477 -160
- data/ext/kobako/src/wasm.rs +20 -5
- data/lib/kobako/capture.rb +12 -10
- data/lib/kobako/codec/decoder.rb +3 -4
- data/lib/kobako/codec/encoder.rb +1 -1
- data/lib/kobako/codec/error.rb +3 -2
- data/lib/kobako/codec/factory.rb +24 -17
- data/lib/kobako/codec/utils.rb +105 -12
- data/lib/kobako/codec.rb +2 -1
- data/lib/kobako/errors.rb +22 -10
- data/lib/kobako/handle.rb +62 -0
- data/lib/kobako/handle_table.rb +119 -0
- data/lib/kobako/invocation.rb +143 -0
- data/lib/kobako/outcome/panic.rb +2 -2
- data/lib/kobako/outcome.rb +61 -24
- data/lib/kobako/rpc/dispatcher.rb +30 -28
- data/lib/kobako/rpc/envelope.rb +10 -10
- data/lib/kobako/rpc/fault.rb +4 -3
- data/lib/kobako/rpc/namespace.rb +3 -3
- data/lib/kobako/rpc/server.rb +23 -33
- data/lib/kobako/rpc/wire_error.rb +23 -0
- data/lib/kobako/sandbox.rb +211 -136
- data/lib/kobako/sandbox_options.rb +73 -0
- data/lib/kobako/snippet/binary.rb +30 -0
- data/lib/kobako/snippet/source.rb +28 -0
- data/lib/kobako/snippet/table.rb +174 -0
- data/lib/kobako/snippet.rb +20 -0
- data/lib/kobako/usage.rb +41 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/sig/kobako/codec/factory.rbs +1 -1
- data/sig/kobako/codec/utils.rbs +10 -0
- data/sig/kobako/errors.rbs +3 -0
- data/sig/kobako/handle.rbs +19 -0
- data/sig/kobako/handle_table.rbs +23 -0
- data/sig/kobako/invocation.rbs +25 -0
- data/sig/kobako/outcome.rbs +1 -1
- data/sig/kobako/rpc/dispatcher.rbs +7 -7
- data/sig/kobako/rpc/envelope.rbs +3 -3
- data/sig/kobako/rpc/server.rbs +1 -7
- data/sig/kobako/rpc/wire_error.rbs +6 -0
- data/sig/kobako/sandbox.rbs +22 -17
- data/sig/kobako/sandbox_options.rbs +32 -0
- data/sig/kobako/snippet/binary.rbs +12 -0
- data/sig/kobako/snippet/source.rbs +13 -0
- data/sig/kobako/snippet/table.rbs +36 -0
- data/sig/kobako/snippet.rbs +4 -0
- data/sig/kobako/usage.rbs +11 -0
- data/sig/kobako/wasm.rbs +5 -1
- metadata +21 -5
- data/lib/kobako/rpc/handle.rb +0 -38
- data/lib/kobako/rpc/handle_table.rb +0 -107
- data/sig/kobako/rpc/handle.rbs +0 -19
- data/sig/kobako/rpc/handle_table.rbs +0 -25
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
# Kobako::SandboxOptions — immutable Value Object holding the four
|
|
5
|
+
# per-Sandbox configuration caps ({docs/behavior.md B-01,
|
|
6
|
+
# E-20}[link:../../docs/behavior.md]). Built on the +class X <
|
|
7
|
+
# Data.define(...)+ subclass form (the Steep-friendly shape — see
|
|
8
|
+
# +lib/kobako/outcome/panic.rb+).
|
|
9
|
+
#
|
|
10
|
+
# The +initialize+ method does double duty: it applies DEFAULT fallback
|
|
11
|
+
# for absent values and normalises (timeout to Float seconds,
|
|
12
|
+
# memory_limit to positive Integer bytes) before delegating to Data's
|
|
13
|
+
# +super+. Anything that survives +SandboxOptions.new+ is a wire-ready
|
|
14
|
+
# cap bundle the +Kobako::Wasm::Instance+ constructor consumes as-is.
|
|
15
|
+
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]).
|
|
18
|
+
DEFAULT_TIMEOUT_SECONDS = 60.0
|
|
19
|
+
|
|
20
|
+
# Default cap on the per-invocation guest linear-memory delta:
|
|
21
|
+
# 1 MiB ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
|
|
22
|
+
# The mruby image's initial allocation and prior invocations'
|
|
23
|
+
# watermark sit outside this budget — see B-01 Notes.
|
|
24
|
+
DEFAULT_MEMORY_LIMIT = 1 << 20
|
|
25
|
+
|
|
26
|
+
# Default per-channel capture ceiling: 1 MiB
|
|
27
|
+
# ({docs/behavior.md B-01}[link:../../docs/behavior.md]).
|
|
28
|
+
DEFAULT_OUTPUT_LIMIT = 1 << 20
|
|
29
|
+
|
|
30
|
+
# steep:ignore:start
|
|
31
|
+
def initialize(timeout: DEFAULT_TIMEOUT_SECONDS,
|
|
32
|
+
memory_limit: DEFAULT_MEMORY_LIMIT,
|
|
33
|
+
stdout_limit: nil,
|
|
34
|
+
stderr_limit: nil)
|
|
35
|
+
super(
|
|
36
|
+
timeout: normalize_timeout(timeout),
|
|
37
|
+
memory_limit: normalize_memory_limit(memory_limit),
|
|
38
|
+
stdout_limit: stdout_limit || DEFAULT_OUTPUT_LIMIT,
|
|
39
|
+
stderr_limit: stderr_limit || DEFAULT_OUTPUT_LIMIT
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Coerce +timeout+ into the Float seconds the ext expects, or +nil+
|
|
46
|
+
# to mean the cap is disabled. Any finite non-positive value is
|
|
47
|
+
# rejected — a zero or negative timeout would either fire instantly
|
|
48
|
+
# or never, both of which would surprise callers more than an early
|
|
49
|
+
# +ArgumentError+.
|
|
50
|
+
def normalize_timeout(timeout)
|
|
51
|
+
return nil if timeout.nil?
|
|
52
|
+
raise ArgumentError, "timeout must be Numeric or nil, got #{timeout.class}" unless timeout.is_a?(Numeric)
|
|
53
|
+
|
|
54
|
+
seconds = timeout.to_f
|
|
55
|
+
raise ArgumentError, "timeout must be > 0 (got #{timeout})" unless seconds.positive? && seconds.finite?
|
|
56
|
+
|
|
57
|
+
seconds
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Coerce +memory_limit+ into the byte cap the ext expects, or +nil+
|
|
61
|
+
# to mean unbounded. Must be a positive Integer when set; Float or
|
|
62
|
+
# zero/negative values are rejected.
|
|
63
|
+
def normalize_memory_limit(memory_limit)
|
|
64
|
+
return nil if memory_limit.nil?
|
|
65
|
+
unless memory_limit.is_a?(Integer) && memory_limit.positive?
|
|
66
|
+
raise ArgumentError, "memory_limit must be a positive Integer or nil, got #{memory_limit.inspect}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
memory_limit
|
|
70
|
+
end
|
|
71
|
+
# steep:ignore:end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Snippet
|
|
5
|
+
# Kobako::Snippet::Binary — value object representing a single
|
|
6
|
+
# +#preload(binary:)+ entry held by +Kobako::Snippet::Table+
|
|
7
|
+
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
8
|
+
#
|
|
9
|
+
# The +body+ is RITE bytecode (as emitted by +mrbc+) carried as an
|
|
10
|
+
# +ASCII_8BIT+ String so msgpack-ruby encodes it as +bin+ family on
|
|
11
|
+
# the wire ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
|
|
12
|
+
# The host treats the bytes as opaque — the snippet's canonical
|
|
13
|
+
# name, when present, lives in the bytecode's embedded
|
|
14
|
+
# +debug_info+ and is resolved by the guest at load time;
|
|
15
|
+
# structural validation
|
|
16
|
+
# ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
|
|
17
|
+
# is deferred to the first invocation's guest replay.
|
|
18
|
+
#
|
|
19
|
+
# The class is a +Data.define+ subclass — frozen and value-equal.
|
|
20
|
+
# Callers (chiefly +Table+) construct instances via keyword form
|
|
21
|
+
# +Binary.new(body: ...)+. Wire-form construction is the +Table+'s
|
|
22
|
+
# responsibility.
|
|
23
|
+
class Binary < Data.define(:body)
|
|
24
|
+
# The +kind+ field value carried by bytecode snippets in their
|
|
25
|
+
# Frame 3 wire envelope entry
|
|
26
|
+
# ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
|
|
27
|
+
KIND = "bytecode"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
module Snippet
|
|
5
|
+
# Kobako::Snippet::Source — value object representing a single
|
|
6
|
+
# +#preload(code:, name:)+ entry held by +Kobako::Snippet::Table+
|
|
7
|
+
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
8
|
+
#
|
|
9
|
+
# +name+ is the canonical +Symbol+ identity baked into the loaded
|
|
10
|
+
# IREP's +debug_info+; backtrace frames originating in this snippet
|
|
11
|
+
# surface as +(snippet:Name):line+. +body+ is the UTF-8 mruby source
|
|
12
|
+
# detached from the caller's reference at +Table#register+ time so
|
|
13
|
+
# later mutation of the original String cannot bleed through.
|
|
14
|
+
#
|
|
15
|
+
# The class is a +Data.define+ subclass — frozen, value-equal, and
|
|
16
|
+
# carries no mutation API. Callers (chiefly +Table+) construct
|
|
17
|
+
# instances via keyword form +Source.new(name: ..., body: ...)+.
|
|
18
|
+
# Wire-form construction is the +Table+'s responsibility, mirroring
|
|
19
|
+
# +Kobako::RPC.encode_request+'s pattern of reading attributes off a
|
|
20
|
+
# carrier rather than asking the carrier to self-describe.
|
|
21
|
+
class Source < Data.define(:name, :body)
|
|
22
|
+
# The +kind+ field value carried by source snippets in their Frame
|
|
23
|
+
# 3 wire envelope entry
|
|
24
|
+
# ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
|
|
25
|
+
KIND = "source"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "msgpack"
|
|
4
|
+
|
|
5
|
+
require_relative "binary"
|
|
6
|
+
require_relative "source"
|
|
7
|
+
|
|
8
|
+
module Kobako
|
|
9
|
+
module Snippet
|
|
10
|
+
# Kobako::Snippet::Table — per-Sandbox insertion-ordered registry of
|
|
11
|
+
# preloaded snippets
|
|
12
|
+
# ({docs/behavior.md B-32 / B-33}[link:../../../docs/behavior.md]).
|
|
13
|
+
#
|
|
14
|
+
# Entries replay against the fresh +mrb_state+ before per-invocation
|
|
15
|
+
# source / entrypoint resolution. Each +Source+ entry's +name+ is its
|
|
16
|
+
# canonical identity — the filename baked into the loaded IREP's
|
|
17
|
+
# +debug_info+ that surfaces in every backtrace frame originating
|
|
18
|
+
# from the snippet as +(snippet:Name):line+. Duplicate names within
|
|
19
|
+
# the +code:+ form would produce ambiguous attribution and are
|
|
20
|
+
# rejected at registration time
|
|
21
|
+
# ({docs/behavior.md E-33}[link:../../../docs/behavior.md]).
|
|
22
|
+
# +Binary+ entries carry no host-side name — their canonical name
|
|
23
|
+
# lives in the bytecode's +debug_info+ and is read by the guest at
|
|
24
|
+
# load time; the host does not extract it.
|
|
25
|
+
#
|
|
26
|
+
# Sealing (B-33) is governed by the owning Sandbox — the table itself
|
|
27
|
+
# is append-only and exposes no mutation API beyond +#register+; the
|
|
28
|
+
# Sandbox guards +#register+ behind the seal check before delegating.
|
|
29
|
+
class Table
|
|
30
|
+
# Ruby constant-name pattern enforced on snippet names
|
|
31
|
+
# ({docs/behavior.md E-34}[link:../../../docs/behavior.md]).
|
|
32
|
+
NAME_PATTERN = /\A[A-Z]\w*\z/
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@entries = [] # : Array[Kobako::Snippet::Source | Kobako::Snippet::Binary]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Serialize the registered snippets to wire bytes. Each entry
|
|
39
|
+
# contributes a msgpack map shape; the collection rides as a single
|
|
40
|
+
# msgpack array. An empty table serializes to an empty array, never
|
|
41
|
+
# absent. The wire codec is an implementation detail — callers
|
|
42
|
+
# receive a binary +String+ that the +Kobako::Wasm+ layer ships
|
|
43
|
+
# through the invocation channel. Mirrors the
|
|
44
|
+
# +Kobako::RPC.encode_request+ pattern: entry value objects stay
|
|
45
|
+
# pure carriers, this method reads their attributes externally.
|
|
46
|
+
def encode
|
|
47
|
+
MessagePack.pack(@entries.map { |entry| entry_payload(entry) })
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Register one preloaded snippet in either of two forms
|
|
51
|
+
# ({docs/behavior.md B-32}[link:../../../docs/behavior.md]).
|
|
52
|
+
#
|
|
53
|
+
# * Source form +register(code: src, name: Name)+ — +src+ is the
|
|
54
|
+
# mruby source as a String; the bytes are re-encoded as UTF-8
|
|
55
|
+
# and detached from the caller's reference. +name+ is a Symbol
|
|
56
|
+
# or String matching +NAME_PATTERN+. Returns the Symbol form
|
|
57
|
+
# of +name+.
|
|
58
|
+
# * Binary form +register(binary: bytes)+ — +bytes+ is
|
|
59
|
+
# precompiled RITE bytecode as a String, duplicated and forced
|
|
60
|
+
# to ASCII-8BIT so msgpack-ruby ships it as +bin+. Returns
|
|
61
|
+
# +nil+ — bytecode entries are anonymous on the host side; any
|
|
62
|
+
# structural validation
|
|
63
|
+
# ({docs/behavior.md E-37 / E-38}[link:../../../docs/behavior.md])
|
|
64
|
+
# is deferred to the guest at first replay.
|
|
65
|
+
#
|
|
66
|
+
# The two forms are mutually exclusive: shape validation lives
|
|
67
|
+
# here so callers (chiefly +Kobako::Sandbox#preload+) collapse to
|
|
68
|
+
# a single delegation. Raises +ArgumentError+ on mixed forms,
|
|
69
|
+
# missing keywords, wrong types, malformed +name+ (E-34), or
|
|
70
|
+
# duplicate +code:+ +name+ (E-33).
|
|
71
|
+
def register(code: nil, name: nil, binary: nil)
|
|
72
|
+
if binary
|
|
73
|
+
raise ArgumentError, "cannot combine binary: with code: / name:" if code || name
|
|
74
|
+
|
|
75
|
+
register_binary!(binary)
|
|
76
|
+
else
|
|
77
|
+
register_source!(code, name)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Iterate over registered entries in insertion order. Yields each
|
|
82
|
+
# entry (a +Kobako::Snippet::Source+ or +Kobako::Snippet::Binary+).
|
|
83
|
+
# Returns an Enumerator when no block is given.
|
|
84
|
+
def each(&)
|
|
85
|
+
@entries.each(&)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Canonical names of every registered +Source+ entry, in insertion
|
|
89
|
+
# order. +Binary+ entries are skipped — their names live in
|
|
90
|
+
# bytecode +debug_info+ on the guest side and are not extracted by
|
|
91
|
+
# the host.
|
|
92
|
+
def names
|
|
93
|
+
@entries.filter_map { |entry| entry.name if entry.is_a?(Source) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Number of registered snippets.
|
|
97
|
+
def size
|
|
98
|
+
@entries.size
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Whether no snippets are registered.
|
|
102
|
+
def empty?
|
|
103
|
+
@entries.empty?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Source-form register path. Delegates argument-shape checks to
|
|
109
|
+
# +ensure_source_args!+ (which returns the narrowed
|
|
110
|
+
# +[code, name]+ pair), normalises +name+ to a Symbol, rejects
|
|
111
|
+
# duplicates (E-33), and appends the Source entry.
|
|
112
|
+
def register_source!(code, name)
|
|
113
|
+
code, name = ensure_source_args!(code, name)
|
|
114
|
+
name_sym = normalize_name(name)
|
|
115
|
+
raise ArgumentError, "snippet #{name_sym.inspect} already preloaded" if names.include?(name_sym)
|
|
116
|
+
|
|
117
|
+
@entries << Source.new(name: name_sym, body: code.dup.force_encoding(Encoding::UTF_8))
|
|
118
|
+
name_sym
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Shape-only validation for the +code:+ + +name:+ pair. Returns
|
|
122
|
+
# the pair with +nil+ narrowed away so callers can treat both as
|
|
123
|
+
# present. The +code:+ type check runs before the +name:+
|
|
124
|
+
# presence check so callers passing +code: nil+ explicitly see
|
|
125
|
+
# the type error rather than the "missing keyword" error.
|
|
126
|
+
def ensure_source_args!(code, name)
|
|
127
|
+
raise ArgumentError, "missing keyword: code: + name:, or binary:" if code.nil? && name.nil?
|
|
128
|
+
raise ArgumentError, "code must be a String, got #{code.class}" unless code.is_a?(String)
|
|
129
|
+
raise ArgumentError, "missing keyword: name:" if name.nil?
|
|
130
|
+
|
|
131
|
+
[code, name]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Binary-form register path. Validates the +binary:+ payload
|
|
135
|
+
# type and appends the Binary entry. The bytes are duplicated and
|
|
136
|
+
# forced to ASCII-8BIT so msgpack-ruby picks the +bin+ family on
|
|
137
|
+
# the wire.
|
|
138
|
+
def register_binary!(bytes)
|
|
139
|
+
raise ArgumentError, "binary must be a String, got #{bytes.class}" unless bytes.is_a?(String)
|
|
140
|
+
|
|
141
|
+
@entries << Binary.new(body: bytes.dup.force_encoding(Encoding::ASCII_8BIT))
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Build the msgpack-ready Hash for one entry. Source entries
|
|
146
|
+
# contribute their host-side +name+; Binary entries omit it
|
|
147
|
+
# because the canonical name lives in the bytecode's embedded
|
|
148
|
+
# +debug_info+ and is read by the guest at load time
|
|
149
|
+
# ({docs/wire-codec.md Invocation channels}[link:../../../docs/wire-codec.md]).
|
|
150
|
+
def entry_payload(entry)
|
|
151
|
+
case entry
|
|
152
|
+
when Source
|
|
153
|
+
{ "name" => entry.name.to_s, "kind" => Source::KIND, "body" => entry.body }
|
|
154
|
+
when Binary
|
|
155
|
+
{ "kind" => Binary::KIND, "body" => entry.body }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def normalize_name(name)
|
|
160
|
+
unless name.is_a?(Symbol) || name.is_a?(String)
|
|
161
|
+
raise ArgumentError, "snippet name must be a Symbol or String, got #{name.class}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
name_str = name.to_s
|
|
165
|
+
unless NAME_PATTERN.match?(name_str)
|
|
166
|
+
raise ArgumentError,
|
|
167
|
+
"snippet name must match #{NAME_PATTERN.inspect} (got #{name.inspect})"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
name_str.to_sym
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "snippet/binary"
|
|
4
|
+
require_relative "snippet/source"
|
|
5
|
+
require_relative "snippet/table"
|
|
6
|
+
|
|
7
|
+
module Kobako
|
|
8
|
+
# Kobako::Snippet — namespace for the per-Sandbox preloaded snippet
|
|
9
|
+
# registry and its entry value objects
|
|
10
|
+
# ({docs/behavior.md B-32 / B-33}[link:../../docs/behavior.md]).
|
|
11
|
+
#
|
|
12
|
+
# The +Table+ owns insertion-ordered storage and seal-coordination with
|
|
13
|
+
# the owning Sandbox; +Source+ is the value object representing a single
|
|
14
|
+
# +#preload(code:, name:)+ entry. Entry types live as siblings under
|
|
15
|
+
# this module rather than nested under +Table+ so they remain plain
|
|
16
|
+
# value objects with no implicit dependency on the registry that holds
|
|
17
|
+
# them.
|
|
18
|
+
module Snippet
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/kobako/usage.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kobako
|
|
4
|
+
# Per-last-invocation resource accounting for a +Kobako::Sandbox+
|
|
5
|
+
# ({docs/behavior.md B-35}[link:../../docs/behavior.md]). Carries two
|
|
6
|
+
# readers populated by every +#eval+ / +#run+ invocation:
|
|
7
|
+
#
|
|
8
|
+
# * +wall_time+ — the Float number of seconds the guest export call
|
|
9
|
+
# spent inside wasmtime during the most recent invocation. The
|
|
10
|
+
# measurement bracket aligns with the +timeout+ deadline
|
|
11
|
+
# ({docs/behavior.md B-01}[link:../../docs/behavior.md]); time spent
|
|
12
|
+
# in host Service callbacks is included, but everything that runs
|
|
13
|
+
# after the guest export returns — the post-export
|
|
14
|
+
# +OUTCOME_BUFFER+ fetch and decode, plus stdout / stderr capture
|
|
15
|
+
# readout — is excluded.
|
|
16
|
+
# * +memory_peak+ — the Integer high-water mark, in bytes, of the
|
|
17
|
+
# per-invocation +memory.grow+ delta past the linear-memory size
|
|
18
|
+
# captured at invocation entry. Same baseline accounting as
|
|
19
|
+
# +memory_limit+ ({docs/behavior.md E-20}[link:../../docs/behavior.md]):
|
|
20
|
+
# the mruby image's initial allocation and any prior-invocation
|
|
21
|
+
# watermark sit outside the measurement. On +MemoryLimitError+
|
|
22
|
+
# +memory_peak+ never exceeds the configured cap because the
|
|
23
|
+
# rejected +desired+ value is not promoted into the high-water.
|
|
24
|
+
#
|
|
25
|
+
# Both readers are populated on every outcome, including +TrapError+
|
|
26
|
+
# branches, so the Host App can read +Sandbox#usage+ after rescuing a
|
|
27
|
+
# trap to diagnose how much of the budget the failing invocation
|
|
28
|
+
# consumed. Before the first invocation +Sandbox#usage+ returns the
|
|
29
|
+
# pre-invocation sentinel +Kobako::Usage::EMPTY+.
|
|
30
|
+
#
|
|
31
|
+
# Built on the +class X < Data.define(...)+ subclass form so the
|
|
32
|
+
# class body is fully Steep-visible; ruby/rbs upstream documents this
|
|
33
|
+
# as the Steep-friendly shape and the +Style/DataInheritance+ cop is
|
|
34
|
+
# disabled on that basis (see +.rubocop.yml+).
|
|
35
|
+
class Usage < Data.define(:wall_time, :memory_peak)
|
|
36
|
+
# Pre-invocation sentinel ({docs/behavior.md B-35}[link:../../docs/behavior.md]).
|
|
37
|
+
# Reused by +Sandbox+ before any invocation has run so callers do not
|
|
38
|
+
# need to handle a +nil+ +#usage+.
|
|
39
|
+
EMPTY = new(wall_time: 0.0, memory_peak: 0)
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/kobako/version.rb
CHANGED
data/lib/kobako.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative "kobako/version"
|
|
|
4
4
|
require "kobako/kobako"
|
|
5
5
|
require_relative "kobako/errors"
|
|
6
6
|
require_relative "kobako/rpc"
|
|
7
|
+
require_relative "kobako/rpc/wire_error"
|
|
7
8
|
require_relative "kobako/rpc/server"
|
|
8
9
|
require_relative "kobako/wasm"
|
|
9
10
|
require_relative "kobako/sandbox"
|
|
@@ -23,7 +23,7 @@ module Kobako
|
|
|
23
23
|
def unpack_symbol: (String payload) -> Symbol
|
|
24
24
|
def register_handle: () -> void
|
|
25
25
|
def register_fault: () -> void
|
|
26
|
-
def unpack_handle: (String payload) -> Kobako::
|
|
26
|
+
def unpack_handle: (String payload) -> Kobako::Handle
|
|
27
27
|
def pack_fault: (Kobako::RPC::Fault fault) -> String
|
|
28
28
|
def unpack_fault: (String payload) -> Kobako::RPC::Fault
|
|
29
29
|
end
|
data/sig/kobako/codec/utils.rbs
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
module Kobako
|
|
2
2
|
module Codec
|
|
3
3
|
module Utils
|
|
4
|
+
MSGPACK_INT_RANGE: Range[Integer]
|
|
5
|
+
|
|
4
6
|
def self?.assert_utf8!: (String string, String label) -> void
|
|
5
7
|
|
|
6
8
|
def self?.wire_boundary: [T] () { () -> T } -> T
|
|
9
|
+
|
|
10
|
+
def self?.wire_representable?: (untyped value) -> bool
|
|
11
|
+
|
|
12
|
+
def self?.deep_wrap: (untyped value, Kobako::HandleTable handle_table) -> untyped
|
|
13
|
+
|
|
14
|
+
def self?.primitive_wire_type?: (untyped value) -> bool
|
|
15
|
+
|
|
16
|
+
def self?.container_wire_representable?: (untyped value) -> bool
|
|
7
17
|
end
|
|
8
18
|
end
|
|
9
19
|
end
|
data/sig/kobako/errors.rbs
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
class Handle
|
|
3
|
+
MIN_ID: Integer
|
|
4
|
+
MAX_ID: Integer
|
|
5
|
+
|
|
6
|
+
attr_reader id: Integer
|
|
7
|
+
|
|
8
|
+
def initialize: (Integer id) -> void
|
|
9
|
+
| (id: Integer) -> void
|
|
10
|
+
|
|
11
|
+
def self.from_wire: (Integer id) -> Handle
|
|
12
|
+
|
|
13
|
+
def with: (?id: Integer) -> Handle
|
|
14
|
+
|
|
15
|
+
def ==: (untyped other) -> bool
|
|
16
|
+
|
|
17
|
+
def hash: () -> Integer
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
class HandleTable
|
|
3
|
+
def initialize: (?next_id: Integer) -> void
|
|
4
|
+
|
|
5
|
+
def alloc: (untyped object) -> Kobako::Handle
|
|
6
|
+
|
|
7
|
+
def fetch: (Integer id) -> untyped
|
|
8
|
+
|
|
9
|
+
def release: (Integer id) -> untyped
|
|
10
|
+
|
|
11
|
+
def reset!: () -> self
|
|
12
|
+
|
|
13
|
+
def mark_disconnected: (Integer id) -> self
|
|
14
|
+
|
|
15
|
+
def size: () -> Integer
|
|
16
|
+
|
|
17
|
+
def include?: (Integer id) -> bool
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def require_bound!: (Integer id) -> void
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Kobako
|
|
2
|
+
class Invocation < Data
|
|
3
|
+
NAME_PATTERN: Regexp
|
|
4
|
+
|
|
5
|
+
attr_reader entrypoint: Symbol
|
|
6
|
+
attr_reader args: Array[untyped]
|
|
7
|
+
attr_reader kwargs: Hash[Symbol, untyped]
|
|
8
|
+
|
|
9
|
+
def self.new: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> Invocation
|
|
10
|
+
|
|
11
|
+
def initialize: (entrypoint: Symbol | String, ?args: Array[untyped], ?kwargs: Hash[untyped, untyped]) -> void
|
|
12
|
+
|
|
13
|
+
def encode: (Kobako::HandleTable handle_table) -> String
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def normalize_entrypoint: (Symbol | String target) -> Symbol
|
|
18
|
+
|
|
19
|
+
def validate_args!: (Array[untyped] args) -> Array[untyped]
|
|
20
|
+
|
|
21
|
+
def validate_kwargs!: (Hash[untyped, untyped] kwargs) -> Hash[Symbol, untyped]
|
|
22
|
+
|
|
23
|
+
def forged_handle_message: (String slot) -> String
|
|
24
|
+
end
|
|
25
|
+
end
|
data/sig/kobako/outcome.rbs
CHANGED
|
@@ -19,6 +19,6 @@ module Kobako
|
|
|
19
19
|
|
|
20
20
|
def self?.panic_target_class: (Panic panic) -> (singleton(SandboxError) | singleton(ServiceError))
|
|
21
21
|
|
|
22
|
-
def self?.build_wire_violation_error: (String message) ->
|
|
22
|
+
def self?.build_wire_violation_error: (String message, ?wire_error: String?) -> RPC::WireError
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -7,25 +7,25 @@ module Kobako
|
|
|
7
7
|
class DisconnectedTargetError < StandardError
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def self?.dispatch: (String request_bytes, Server server) -> String
|
|
10
|
+
def self?.dispatch: (String request_bytes, Server server, Kobako::HandleTable handle_table) -> String
|
|
11
11
|
|
|
12
12
|
def self?.encode_caught_error: (StandardError error) -> String
|
|
13
13
|
|
|
14
14
|
def self?.invoke: (untyped target, String method, Array[untyped] args, Hash[Symbol, untyped] kwargs) -> untyped
|
|
15
15
|
|
|
16
|
-
def self?.resolve_arg: (untyped value, HandleTable handle_table) -> untyped
|
|
16
|
+
def self?.resolve_arg: (untyped value, Kobako::HandleTable handle_table) -> untyped
|
|
17
17
|
|
|
18
|
-
def self?.resolve_target: (String | Kobako::
|
|
18
|
+
def self?.resolve_target: (String | Kobako::Handle target, Server server, Kobako::HandleTable handle_table) -> untyped
|
|
19
19
|
|
|
20
20
|
def self?.resolve_path: (String path, Server server) -> untyped
|
|
21
21
|
|
|
22
|
-
def self?.resolve_handle: (Kobako::
|
|
22
|
+
def self?.resolve_handle: (Kobako::Handle handle, Kobako::HandleTable handle_table) -> untyped
|
|
23
23
|
|
|
24
|
-
def self?.require_live_object!: (Integer id, HandleTable handle_table) -> untyped
|
|
24
|
+
def self?.require_live_object!: (Integer id, Kobako::HandleTable handle_table) -> untyped
|
|
25
25
|
|
|
26
|
-
def self?.encode_ok: (untyped value,
|
|
26
|
+
def self?.encode_ok: (untyped value, Kobako::HandleTable handle_table) -> String
|
|
27
27
|
|
|
28
|
-
def self?.wrap_as_handle: (untyped value,
|
|
28
|
+
def self?.wrap_as_handle: (untyped value, Kobako::HandleTable handle_table) -> Kobako::Handle
|
|
29
29
|
|
|
30
30
|
def self?.encode_error: (String type, String message) -> String
|
|
31
31
|
end
|
data/sig/kobako/rpc/envelope.rbs
CHANGED
|
@@ -4,14 +4,14 @@ module Kobako
|
|
|
4
4
|
STATUS_ERROR: Integer
|
|
5
5
|
|
|
6
6
|
class Request
|
|
7
|
-
attr_reader target: String | Kobako::
|
|
7
|
+
attr_reader target: String | Kobako::Handle
|
|
8
8
|
attr_reader method_name: String
|
|
9
9
|
attr_reader args: Array[untyped]
|
|
10
10
|
attr_reader kwargs: Hash[Symbol, untyped]
|
|
11
11
|
|
|
12
|
-
def initialize: (target: String | Kobako::
|
|
12
|
+
def initialize: (target: String | Kobako::Handle, method: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped]) -> void
|
|
13
13
|
|
|
14
|
-
def with: (?target: String | Kobako::
|
|
14
|
+
def with: (?target: String | Kobako::Handle, ?method: String, ?args: Array[untyped], ?kwargs: Hash[Symbol, untyped]) -> Request
|
|
15
15
|
|
|
16
16
|
def ==: (untyped other) -> bool
|
|
17
17
|
|
data/sig/kobako/rpc/server.rbs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
module Kobako
|
|
2
2
|
module RPC
|
|
3
3
|
class Server
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def initialize: (?handle_table: HandleTable) -> void
|
|
4
|
+
def initialize: (?handle_table: Kobako::HandleTable) -> void
|
|
7
5
|
|
|
8
6
|
def define: (Symbol | String name) -> Kobako::RPC::Namespace
|
|
9
7
|
|
|
@@ -11,8 +9,6 @@ module Kobako
|
|
|
11
9
|
|
|
12
10
|
def bound?: (String target) -> bool
|
|
13
11
|
|
|
14
|
-
def namespaces: () -> Array[Kobako::RPC::Namespace]
|
|
15
|
-
|
|
16
12
|
def size: () -> Integer
|
|
17
13
|
|
|
18
14
|
def empty?: () -> bool
|
|
@@ -25,8 +21,6 @@ module Kobako
|
|
|
25
21
|
|
|
26
22
|
def sealed?: () -> bool
|
|
27
23
|
|
|
28
|
-
def reset_handles!: () -> HandleTable
|
|
29
|
-
|
|
30
24
|
def dispatch: (String request_bytes) -> String
|
|
31
25
|
|
|
32
26
|
private
|
data/sig/kobako/sandbox.rbs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
module Kobako
|
|
2
2
|
class Sandbox
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
DEFAULT_TIMEOUT_SECONDS: Float
|
|
6
|
-
|
|
7
|
-
DEFAULT_MEMORY_LIMIT: Integer
|
|
3
|
+
extend Forwardable
|
|
8
4
|
|
|
9
5
|
attr_reader wasm_path: String
|
|
10
6
|
attr_reader instance: Kobako::Wasm::Instance
|
|
11
|
-
attr_reader
|
|
12
|
-
attr_reader stderr_limit: Integer
|
|
13
|
-
attr_reader timeout: Float?
|
|
14
|
-
attr_reader memory_limit: Integer?
|
|
7
|
+
attr_reader options: Kobako::SandboxOptions
|
|
15
8
|
attr_reader services: Kobako::RPC::Server
|
|
9
|
+
attr_reader snippets: Kobako::Snippet::Table
|
|
10
|
+
|
|
11
|
+
# Forwarded to @options via Forwardable#def_delegators.
|
|
12
|
+
def timeout: () -> Float?
|
|
13
|
+
def memory_limit: () -> Integer?
|
|
14
|
+
def stdout_limit: () -> Integer
|
|
15
|
+
def stderr_limit: () -> Integer
|
|
16
16
|
|
|
17
17
|
def initialize: (
|
|
18
18
|
?wasm_path: String?,
|
|
@@ -30,24 +30,29 @@ module Kobako
|
|
|
30
30
|
|
|
31
31
|
def stderr_truncated?: () -> bool
|
|
32
32
|
|
|
33
|
+
attr_reader usage: Kobako::Usage
|
|
34
|
+
|
|
33
35
|
def define: (Symbol | String name) -> Kobako::RPC::Namespace
|
|
34
36
|
|
|
35
|
-
def
|
|
37
|
+
def preload: (code: String, name: Symbol | String) -> Kobako::Sandbox
|
|
38
|
+
| (binary: String) -> Kobako::Sandbox
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
def run: (Symbol | String target, *untyped args, **untyped kwargs) -> untyped
|
|
38
41
|
|
|
39
|
-
def
|
|
42
|
+
def eval: (String code) -> untyped
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
private
|
|
42
45
|
|
|
43
|
-
def
|
|
46
|
+
def reset_invocation_state!: () -> void
|
|
44
47
|
|
|
45
|
-
def
|
|
48
|
+
def begin_invocation!: () -> void
|
|
46
49
|
|
|
47
50
|
def read_captures!: () -> void
|
|
48
51
|
|
|
49
|
-
def
|
|
52
|
+
def read_usage!: () -> void
|
|
53
|
+
|
|
54
|
+
def trap_class_for: (Kobako::Wasm::Error err) -> singleton(Kobako::TrapError)
|
|
50
55
|
|
|
51
|
-
def
|
|
56
|
+
def invoke!: (Symbol verb) { () -> void } -> untyped
|
|
52
57
|
end
|
|
53
58
|
end
|