Class: Kobako::Sandbox
- Inherits:
-
Object
- Object
- Kobako::Sandbox
- Extended by:
- Forwardable
- Defined in:
- lib/kobako/sandbox.rb
Overview
Kobako::Sandbox — the user-facing entry point for executing guest mruby scripts inside a wasmtime-hosted Wasm module (docs/behavior.md B-01).
The Sandbox owns the Kobako::Runtime, the per-Sandbox Kobako::Catalog::Handles (docs/behavior.md B-19), the per-instance Kobako::Catalog::Namespaces (which receives the Catalog::Handles by injection so guest→host dispatch and host→guest auto-wrap share one allocator), and the dispatch Proc / yield_to_guest lambda installed on the Runtime via Runtime#on_dispatch= (docs/behavior.md B-12). The underlying wasmtime Engine and compiled Module are cached at process scope by the native ext and never surface to Ruby — constructing many Sandboxes amortises both costs automatically.
Output capture policy (docs/behavior.md B-04): the per-channel cap (stdout_limit / stderr_limit) is enforced inside the WASI pipe — the host buffer stops growing at the cap, subsequent guest writes on that channel fail or are dropped, and #run still returns normally. #stdout / #stderr return the captured prefix as a UTF-8 String; the byte content never carries a truncation sentinel. #stdout_truncated? / #stderr_truncated? are the only way to observe that the cap was hit.
Instance Attribute Summary collapse
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#usage ⇒ Object
readonly
Returns the
Kobako::Usagevalue object for the most recent invocation (docs/behavior.md B-35). -
#wasm_path ⇒ Object
readonly
Returns the value of attribute wasm_path.
Instance Method Summary collapse
-
#define(name) ⇒ Object
Declare or retrieve the Namespace named
nameon this Sandbox (docs/behavior.md B-07, B-09, B-10). -
#eval(code) ⇒ Object
Execute a guest mruby source string in a fresh
mrb_state(docs/behavior.md B-02 / B-03 / B-06). -
#initialize(wasm_path: nil, stdout_limit: nil, stderr_limit: nil, timeout: SandboxOptions::DEFAULT_TIMEOUT_SECONDS, memory_limit: SandboxOptions::DEFAULT_MEMORY_LIMIT) ⇒ Sandbox
constructor
Build a fresh Sandbox.
-
#preload(code: nil, name: nil, binary: nil) ⇒ Object
Register a snippet on this Sandbox in one of two forms (docs/behavior.md B-32):.
-
#run(target, *args, **kwargs) ⇒ Object
Dispatch into a preloaded entrypoint constant (docs/behavior.md B-31).
-
#stderr ⇒ Object
Returns the bytes the guest wrote to stderr during the most recent invocation as a UTF-8 String, clipped at
stderr_limit. -
#stderr_truncated? ⇒ Boolean
Returns
trueiff stderr capture during the most recent invocation exceededstderr_limit. -
#stdout ⇒ Object
Returns the bytes the guest wrote to stdout during the most recent invocation as a UTF-8 String, clipped at
stdout_limit. -
#stdout_truncated? ⇒ Boolean
Returns
trueiff stdout capture during the most recent invocation exceededstdout_limit(docs/behavior.md B-04).
Constructor Details
#initialize(wasm_path: nil, stdout_limit: nil, stderr_limit: nil, timeout: SandboxOptions::DEFAULT_TIMEOUT_SECONDS, memory_limit: SandboxOptions::DEFAULT_MEMORY_LIMIT) ⇒ Sandbox
Build a fresh Sandbox.
wasm_path is the absolute path to the Guest Binary; defaults to the gem-bundled data/kobako.wasm. The four caps (stdout_limit, stderr_limit, timeout, memory_limit) are forwarded verbatim to Kobako::SandboxOptions, which owns their DEFAULT fallback and normalisation. The constructed SandboxOptions is exposed as #options and the four caps remain readable directly on Sandbox via Forwardable delegation.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/kobako/sandbox.rb', line 96 def initialize(wasm_path: nil, stdout_limit: nil, stderr_limit: nil, timeout: SandboxOptions::DEFAULT_TIMEOUT_SECONDS, memory_limit: SandboxOptions::DEFAULT_MEMORY_LIMIT) @wasm_path = wasm_path || Kobako::Runtime.default_path @options = SandboxOptions.new(timeout: timeout, memory_limit: memory_limit, stdout_limit: stdout_limit, stderr_limit: stderr_limit) @handler = Catalog::Handles.new @services = Kobako::Catalog::Namespaces.new(handler: @handler) @snippets = Catalog::Snippets.new @runtime = Kobako::Runtime.from_path(@wasm_path, @options.timeout, @options.memory_limit, @options.stdout_limit, @options.stderr_limit) install_dispatch_proc! reset_invocation_state! end |
Instance Attribute Details
#options ⇒ Object (readonly)
Returns the value of attribute options.
40 41 42 |
# File 'lib/kobako/sandbox.rb', line 40 def @options end |
#usage ⇒ Object (readonly)
Returns the Kobako::Usage value object for the most recent invocation (docs/behavior.md B-35). Carries wall_time (Float seconds the guest export call spent inside wasmtime) and memory_peak (Integer bytes, high-water of the per-invocation memory.grow delta past the entry-time baseline). Returns Kobako::Usage::EMPTY before any invocation; populated on every outcome — including TrapError — so the Host App can read it after rescuing a trap to diagnose budget consumption.
85 86 87 |
# File 'lib/kobako/sandbox.rb', line 85 def usage @usage end |
#wasm_path ⇒ Object (readonly)
Returns the value of attribute wasm_path.
40 41 42 |
# File 'lib/kobako/sandbox.rb', line 40 def wasm_path @wasm_path end |
Instance Method Details
#define(name) ⇒ Object
Declare or retrieve the Namespace named name on this Sandbox (docs/behavior.md B-07, B-09, B-10). name must be a Symbol or String in constant form. Returns the Kobako::Namespace.
Raises ArgumentError when called after the first invocation, or when name does not match the constant-name pattern.
118 119 120 |
# File 'lib/kobako/sandbox.rb', line 118 def define(name) @services.define(name) end |
#eval(code) ⇒ Object
Execute a guest mruby source string in a fresh mrb_state (docs/behavior.md B-02 / B-03 / B-06). code is the mruby source as a UTF-8 String. Returns the deserialized last expression of the source.
Source delivery uses the WASI stdin three-frame protocol (docs/wire-codec.md Invocation channels): Frame 1 carries the msgpack-encoded preamble (Namespace / Member registry snapshot), Frame 2 carries the user source UTF-8 bytes, and Frame 3 carries the snippet table registered via #preload (B-32). Each frame is prefixed by a 4-byte big-endian u32 length; Frame 3 is mandatory-presence — an empty snippet table sends an empty msgpack array, never an absent frame.
The first invocation seals the Service registry and snippet table (docs/behavior.md B-07 / B-33); subsequent #define / #preload calls raise ArgumentError.
Raises Kobako::TrapError on a Wasm trap or wire-violation fallback; Kobako::SandboxError when the guest ran to completion but failed (including when code is nil or not a String, or when a preloaded snippet’s replay raises — E-36); Kobako::ServiceError on an unrescued Service capability failure.
200 201 202 203 204 205 206 |
# File 'lib/kobako/sandbox.rb', line 200 def eval(code) raise SandboxError, "code must be a String, got #{code.class}" unless code.is_a?(String) invoke!(:eval) do @runtime.eval(@services.encode, code.b, @snippets.encode) end end |
#preload(code: nil, name: nil, binary: nil) ⇒ Object
Register a snippet on this Sandbox in one of two forms (docs/behavior.md B-32):
* +preload(code: source, name: Name)+ — +source+ is mruby source
as a +String+ and +Name+ matches +/\A[A-Z]\w*\z/+. The +name+
becomes the snippet's +(snippet:Name)+ backtrace filename and
is the dedupe key for E-33.
* +preload(binary: bytes)+ — +bytes+ is precompiled RITE
bytecode as a +String+. The canonical name, when present,
lives in the bytecode's embedded +debug_info+ and is resolved
by the guest at load time; the host treats the bytes as
opaque. Structural failures
({docs/behavior.md E-37 / E-38}[link:../../docs/behavior.md])
surface as +Kobako::BytecodeError+ on the first invocation.
Subsequent invocations (#eval or #run) replay every registered snippet — in insertion order — against the fresh mrb_state before per-invocation source or entrypoint resolution.
Returns self to allow chaining.
Raises ArgumentError when neither form’s keyword set is supplied, when both forms are mixed (e.g., code: and binary: together, or binary: paired with name:), when code / bytes is not a String, when name does not match the constant pattern (docs/behavior.md E-34), when name duplicates an already-registered code: form snippet (docs/behavior.md E-33), or when called after the first invocation (docs/behavior.md E-35, B-33).
152 153 154 155 156 157 |
# File 'lib/kobako/sandbox.rb', line 152 def preload(code: nil, name: nil, binary: nil) raise ArgumentError, "cannot preload after first Sandbox invocation" if @services.sealed? @snippets.register(code: code, name: name, binary: binary) self end |
#run(target, *args, **kwargs) ⇒ Object
Dispatch into a preloaded entrypoint constant (docs/behavior.md B-31). Delegates host pre-flight and wire encoding to Kobako::Transport::Run / Kobako::Transport::Run#encode: a non-Symbol/String target raises TypeError (E-24), while a target failing the constant pattern (E-25), a forged Kobako::Handle in args / kwargs (E-29), or a non-Symbol kwargs key (E-30) raise ArgumentError. The guest resolves target as a top-level constant, calls #call on it with args / kwargs, and returns the deserialized result. The first invocation seals the Service registry and snippet table (B-07 / B-33). Runtime errors follow the same three-class taxonomy as #eval.
170 171 172 173 174 175 |
# File 'lib/kobako/sandbox.rb', line 170 def run(target, *args, **kwargs) run_envelope = Transport::Run.new(entrypoint: target, args: args, kwargs: kwargs) invoke!(:run) do @runtime.run(@services.encode, @snippets.encode, run_envelope.encode(@handler)) end end |
#stderr ⇒ Object
Returns the bytes the guest wrote to stderr during the most recent invocation as a UTF-8 String, clipped at stderr_limit. Empty before any invocation. Mirror of #stdout.
58 59 60 |
# File 'lib/kobako/sandbox.rb', line 58 def stderr @stderr_capture.bytes end |
#stderr_truncated? ⇒ Boolean
Returns true iff stderr capture during the most recent invocation exceeded stderr_limit. Mirror of #stdout_truncated?.
72 73 74 |
# File 'lib/kobako/sandbox.rb', line 72 def stderr_truncated? @stderr_capture.truncated? end |
#stdout ⇒ Object
Returns the bytes the guest wrote to stdout during the most recent invocation as a UTF-8 String, clipped at stdout_limit. Empty before any invocation. docs/behavior.md B-04 — the byte content never contains a truncation sentinel; use #stdout_truncated? to observe overflow.
51 52 53 |
# File 'lib/kobako/sandbox.rb', line 51 def stdout @stdout_capture.bytes end |
#stdout_truncated? ⇒ Boolean
Returns true iff stdout capture during the most recent invocation exceeded stdout_limit (docs/behavior.md B-04). Resets to false at the start of the next invocation (docs/behavior.md B-03).
66 67 68 |
# File 'lib/kobako/sandbox.rb', line 66 def stdout_truncated? @stdout_capture.truncated? end |