kobako 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/Cargo.lock +1 -1
- data/README.md +14 -7
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +2 -2
- data/ext/kobako/src/runtime/ambient.rs +1 -1
- data/ext/kobako/src/runtime/cache.rs +3 -3
- data/ext/kobako/src/runtime/capture.rs +1 -1
- data/ext/kobako/src/runtime/config.rs +3 -4
- data/ext/kobako/src/runtime/dispatch.rs +6 -6
- data/ext/kobako/src/runtime/exports.rs +1 -1
- data/ext/kobako/src/runtime/instance_pre.rs +1 -1
- data/ext/kobako/src/runtime/invocation.rs +27 -27
- data/ext/kobako/src/runtime/trap.rs +5 -5
- data/ext/kobako/src/runtime.rs +44 -45
- data/ext/kobako/src/snapshot.rs +2 -2
- 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 +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc8e33cc57bfd43cf4d4e06def6536c8c0d45fd4ccd5bbf04b13a4187db67f0a
|
|
4
|
+
data.tar.gz: 8656976c144bb23226686b2c8f60830d3fd635f637c52caaa824bd3e168e07f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35258fb35a0accee9beea36f01c4fb6cb2c658a2aefcb3ae4ebb7c17decd4e97f1b35e155121ae77456eed6d529d7ec13195ba43a3bf8a3ad43724d11ae5512b
|
|
7
|
+
data.tar.gz: 06ea66fd5877fc58fc6e5a21805d434e53968004e8c00577264ca547b10ae7aa4f4196798a3fa18da332fd730cc8fe441ac8e37dbfe2527653fed0da598d8bbf
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.11.0","wasm/kobako-core":"0.5.0","wasm/kobako":"0.5.0","wasm/kobako-io":"0.5.0","wasm/kobako-regexp":"0.5.0","wasm/kobako-baker":"0.5.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0](https://github.com/elct9620/kobako/compare/v0.10.0...v0.11.0) (2026-06-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **transport:** narrow guest-reachable methods via respond_to_guest? ([7a25fe3](https://github.com/elct9620/kobako/commit/7a25fe3b440b523b8a692b7601d08877b1305b0d))
|
|
9
|
+
|
|
3
10
|
## [0.10.0](https://github.com/elct9620/kobako/compare/v0.9.2...v0.10.0) (2026-06-12)
|
|
4
11
|
|
|
5
12
|
|
data/Cargo.lock
CHANGED
data/README.md
CHANGED
|
@@ -69,7 +69,7 @@ The script executes inside the Wasm guest. It cannot read your filesystem, open
|
|
|
69
69
|
|
|
70
70
|
### Services
|
|
71
71
|
|
|
72
|
-
Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior.md`](docs/behavior.md) B-07..B-12.
|
|
72
|
+
Declare a Namespace, then `bind` any Ruby object as a Member; the guest reaches it as a `<Namespace>::<Member>` proxy and invokes its public methods through the Transport wire. See [`docs/behavior/registration.md`](docs/behavior/registration.md) B-07..B-12.
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
75
|
class User
|
|
@@ -93,7 +93,7 @@ Names must match `/\A[A-Z]\w*\z/`. Symbol kwargs travel transparently to the hos
|
|
|
93
93
|
|
|
94
94
|
### Output Capture
|
|
95
95
|
|
|
96
|
-
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior.md`](docs/behavior.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
96
|
+
Guest writes through `puts` / `print` / `p` / `$stdout` / `$stderr` are buffered per-channel and exposed independently of the return value ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-04). Buffers clear at the start of each invocation; overflow is clipped at the cap and flagged by `#stdout_truncated?` / `#stderr_truncated?`.
|
|
97
97
|
|
|
98
98
|
```ruby
|
|
99
99
|
result = sandbox.eval(<<~RUBY)
|
|
@@ -134,7 +134,7 @@ end
|
|
|
134
134
|
|
|
135
135
|
### Resource Limits
|
|
136
136
|
|
|
137
|
-
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior.md`](docs/behavior.md) B-35).
|
|
137
|
+
Each invocation enforces a wall-clock `timeout` and a per-invocation linear-memory `memory_limit`; exhaustion raises a `TrapError` subclass. Pass `nil` to `timeout` / `memory_limit` to disable that cap. Read [`Sandbox#usage`](lib/kobako/sandbox.rb) after the call — populated on every outcome including traps — for actual consumption ([`docs/behavior/lifecycle.md`](docs/behavior/lifecycle.md) B-35).
|
|
138
138
|
|
|
139
139
|
```ruby
|
|
140
140
|
sandbox = Kobako::Sandbox.new(
|
|
@@ -202,7 +202,9 @@ For workloads that must be isolated from each other (one Sandbox per tenant, per
|
|
|
202
202
|
|
|
203
203
|
### Pooling
|
|
204
204
|
|
|
205
|
-
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior.md`](docs/behavior.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
205
|
+
For hosts that serve many short invocations, `Kobako::Pool` keeps a bounded set of warm, identically set-up Sandboxes and hands each one to a single exclusive holder at a time ([`docs/behavior/runtime.md`](docs/behavior/runtime.md) B-46..B-48). Construction forwards every `Sandbox.new` keyword verbatim; the optional block is the per-Sandbox setup window and runs exactly once per constructed Sandbox.
|
|
206
|
+
|
|
207
|
+
`Kobako::Pool` is experimental today and is best treated as a convenience for warm, pre-configured reuse rather than a throughput optimisation. B-49 bakes the shared boot state into the artifact and every dynamic script still compiles and runs per invocation, so all a pool actually saves is the ~30 µs host-side `Sandbox.new`. For the workload kobako is built for — many small, short-lived Sandboxes running dynamic scripts — that is not a significant gain (~4-5% in the [serverless example](examples/serverless/README.md), and proportionally less once the script itself does real work).
|
|
206
208
|
|
|
207
209
|
```ruby
|
|
208
210
|
pool = Kobako::Pool.new(slots: 4) do |sandbox|
|
|
@@ -221,7 +223,7 @@ Sandboxes construct lazily on first demand. `#with` yields a Sandbox with empty
|
|
|
221
223
|
|
|
222
224
|
### Service Blocks
|
|
223
225
|
|
|
224
|
-
A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`docs/behavior.md`](docs/behavior.md) B-23..B-30.
|
|
226
|
+
A Service method can accept a guest-supplied block via `&blk` and `yield` into it. The block body runs inside the Wasm guest; `break` / `next` / exceptions follow normal Ruby semantics, scoped to the single dispatch. See [`docs/behavior/yield.md`](docs/behavior/yield.md) B-23..B-30.
|
|
225
227
|
|
|
226
228
|
```ruby
|
|
227
229
|
sandbox.define(:Seq).bind(:Map, ->(items, &blk) { items.map(&blk) })
|
|
@@ -232,7 +234,7 @@ sandbox.eval('Seq::Map.call([1, 2, 3]) { |x| x * 2 }')
|
|
|
232
234
|
|
|
233
235
|
### Handle Management
|
|
234
236
|
|
|
235
|
-
A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior.md`](docs/behavior.md) B-13..B-21, B-34, B-37).
|
|
237
|
+
A non-wire-representable host object — returned from a Service (B-14), passed to `#run` (B-34), or handed back from the guest (B-37) — crosses the boundary as an opaque `Kobako::Handle` proxy and is restored to the original object before host code sees it; any other unrepresentable value raises `Kobako::SandboxError`. Handles are scoped to a single invocation ([`docs/behavior/dispatch.md`](docs/behavior/dispatch.md) B-13..B-21, B-34, B-37).
|
|
236
238
|
|
|
237
239
|
```ruby
|
|
238
240
|
class Greeter
|
|
@@ -268,7 +270,7 @@ This is deliberate, not a leak. Handle IDs run to 2³¹ − 1 per invocation and
|
|
|
268
270
|
|
|
269
271
|
### Snippets & Entrypoints
|
|
270
272
|
|
|
271
|
-
`Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior.md`](docs/behavior.md) B-31..B-33).
|
|
273
|
+
`Sandbox#preload` registers named mruby snippets that replay into every invocation's canonical boot state; `Sandbox#run(:Target, *args, **kwargs)` dispatches into a top-level `Object` constant defined by those snippets ([`docs/behavior/invocation.md`](docs/behavior/invocation.md) B-31..B-33).
|
|
272
274
|
|
|
273
275
|
```ruby
|
|
274
276
|
sandbox = Kobako::Sandbox.new
|
|
@@ -320,6 +322,11 @@ sandbox.define(:Cfg).bind(:Settings, ThemeReader.new) # not: bind(:Settings, Ap
|
|
|
320
322
|
sandbox.eval('Cfg::Settings.color') # => "#3366ff" — every other method raises NoMethodError
|
|
321
323
|
```
|
|
322
324
|
|
|
325
|
+
When a purpose-built wrapper is more than you need, an object can gate its own surface in
|
|
326
|
+
place: a private `respond_to_guest?(name)` answers, per method, whether the guest may call
|
|
327
|
+
it. Returning `false` for every name makes the object opaque — a credential the guest
|
|
328
|
+
forwards to another Service but never reads — while a named subset becomes an allow-list.
|
|
329
|
+
|
|
323
330
|
Guest code can name any `<Namespace>::<Member>` path, but a forged name only resolves to
|
|
324
331
|
something you bound — the real authorization gate is this host-side allowlist. Give each
|
|
325
332
|
trust context its own Sandbox, and see [`docs/security.md`](docs/security.md) for the rest
|
data/data/kobako.wasm
CHANGED
|
Binary file
|
data/ext/kobako/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "kobako"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.11.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["Aotokitsuruya <contact@aotoki.me>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -27,7 +27,7 @@ wasmtime = { version = "45.0.0", default-features = false, features = [
|
|
|
27
27
|
"wat",
|
|
28
28
|
] }
|
|
29
29
|
# wasmtime-wasi provides WASI preview1 support for routing guest stdout/stderr
|
|
30
|
-
# into in-memory buffers
|
|
30
|
+
# into in-memory buffers. The `p1` feature enables the
|
|
31
31
|
# WasiCtxBuilder + preview1 adapter which wires fd 1/2 to pipes. We omit
|
|
32
32
|
# `p2` (component-model) and `p0`/`p3` (async) because kobako runs
|
|
33
33
|
# synchronous sandboxes only.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//!
|
|
12
12
|
//! The host wall-clock cap is unaffected: the per-invocation timeout runs on
|
|
13
13
|
//! wasmtime epoch interruption against a host `Instant`, never the guest's
|
|
14
|
-
//! frozen `wasi:clocks/monotonic-clock
|
|
14
|
+
//! frozen `wasi:clocks/monotonic-clock`.
|
|
15
15
|
|
|
16
16
|
use std::time::Duration;
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//!
|
|
12
12
|
//! Across processes, the Cranelift compile cost is amortised by a
|
|
13
13
|
//! best-effort `.cwasm` disk cache keyed by the SHA-256 of the Guest
|
|
14
|
-
//! Binary bytes
|
|
14
|
+
//! Binary bytes; every cache failure falls
|
|
15
15
|
//! back to in-process compilation.
|
|
16
16
|
//!
|
|
17
17
|
//! Concurrency: under Ruby's GVL only one thread can execute Rust code
|
|
@@ -37,7 +37,7 @@ static SHARED_ENGINE: OnceLock<WtEngine> = OnceLock::new();
|
|
|
37
37
|
static MODULE_CACHE: OnceLock<Mutex<HashMap<PathBuf, WtModule>>> = OnceLock::new();
|
|
38
38
|
|
|
39
39
|
/// Ticker cadence for the process-singleton epoch ticker. Bounds the
|
|
40
|
-
/// granularity of the
|
|
40
|
+
/// granularity of the wall-clock timeout: the
|
|
41
41
|
/// `epoch_deadline_callback` fires once per tick (`Continue(1)`), so the
|
|
42
42
|
/// trap can lag the deadline by at most one tick under nominal
|
|
43
43
|
/// scheduling. 10 ms keeps the lag small enough that it does not skew
|
|
@@ -57,7 +57,7 @@ const EPOCH_TICK: Duration = Duration::from_millis(10);
|
|
|
57
57
|
///
|
|
58
58
|
/// Also enables `epoch_interruption(true)` so every Store can install an
|
|
59
59
|
/// `epoch_deadline_callback` for the per-run wall-clock cap
|
|
60
|
-
///
|
|
60
|
+
/// cap. The first call spawns the process-singleton ticker
|
|
61
61
|
/// thread that drives `engine.increment_epoch()` at `EPOCH_TICK`
|
|
62
62
|
/// cadence; subsequent calls reuse the same engine and ticker.
|
|
63
63
|
pub(crate) fn shared_engine() -> Result<&'static WtEngine, MagnusError> {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//! Per-channel stdout / stderr capture sizing and clipping.
|
|
2
2
|
//!
|
|
3
|
-
//! Two pure helpers shared by the run path
|
|
3
|
+
//! Two pure helpers shared by the run path: one
|
|
4
4
|
//! sizes the per-run `MemoryOutputPipe`, the other clips a captured
|
|
5
5
|
//! snapshot back to the configured cap and reports whether the cap was
|
|
6
6
|
//! exceeded. Kept channel-agnostic (a function of `cap`, not of which
|
|
@@ -13,11 +13,10 @@ use std::time::Duration;
|
|
|
13
13
|
/// Wall-clock and output caps for one `Runtime`. `None` on any field
|
|
14
14
|
/// disables that cap.
|
|
15
15
|
pub(crate) struct Config {
|
|
16
|
-
/// Wall-clock cap for one guest `#eval` / `#run
|
|
17
|
-
///
|
|
18
|
-
/// `Runtime::prime_caps`.
|
|
16
|
+
/// Wall-clock cap for one guest `#eval` / `#run`. Stamped into a
|
|
17
|
+
/// per-run `Instant` deadline by `Runtime::prime_caps`.
|
|
19
18
|
pub(crate) timeout: Option<Duration>,
|
|
20
|
-
/// Byte cap for guest stdout capture
|
|
19
|
+
/// Byte cap for guest stdout capture.
|
|
21
20
|
/// Sizes the per-run `MemoryOutputPipe` and computes the truncation
|
|
22
21
|
/// flag in `Runtime::build_snapshot`.
|
|
23
22
|
pub(crate) stdout_limit_bytes: Option<usize>,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//! When the guest invokes the wasm import declared in
|
|
4
4
|
//! `wasm/kobako-core/src/abi.rs`, wasmtime calls back into the host
|
|
5
5
|
//! through the closure registered by `instance_pre::build_linker`.
|
|
6
|
-
//! That closure delegates here. The dispatcher
|
|
6
|
+
//! That closure delegates here. The dispatcher:
|
|
7
7
|
//!
|
|
8
8
|
//! 1. Reads the Request bytes from guest linear memory.
|
|
9
9
|
//! 2. Invokes the Ruby-side dispatch Proc bound via
|
|
@@ -55,8 +55,8 @@ use wasmtime::Caller;
|
|
|
55
55
|
use super::invocation::Invocation;
|
|
56
56
|
|
|
57
57
|
// ============================================================
|
|
58
|
-
// Active-caller pointer for the per-thread Invocation slot
|
|
59
|
-
// SPEC.md Single-Invocation Slot).
|
|
58
|
+
// Active-caller pointer for the per-thread Invocation slot
|
|
59
|
+
// (SPEC.md Single-Invocation Slot).
|
|
60
60
|
// ============================================================
|
|
61
61
|
//
|
|
62
62
|
// `Runtime#yield_to_active_invocation` (whose body is the
|
|
@@ -73,8 +73,8 @@ use super::invocation::Invocation;
|
|
|
73
73
|
// The pointer is therefore erased to `NonNull<()>` and parked in a
|
|
74
74
|
// per-thread slot — the materialised form of the SPEC.md
|
|
75
75
|
// "Single-Invocation Slot" invariant. The single-threaded wasm
|
|
76
|
-
// execution per Sandbox
|
|
77
|
-
// dispatch frames
|
|
76
|
+
// execution per Sandbox plus the LIFO re-entry shape of nested
|
|
77
|
+
// dispatch frames ensures no aliasing across threads or across
|
|
78
78
|
// frames; the recovery invariant lives at `current_caller`. The
|
|
79
79
|
// pointer is set on entry to `handle` and restored to the outer
|
|
80
80
|
// frame's value on every exit through a drop guard.
|
|
@@ -85,7 +85,7 @@ thread_local! {
|
|
|
85
85
|
|
|
86
86
|
/// RAII guard that saves the previous `ACTIVE_CALLER` value on
|
|
87
87
|
/// installation and restores it on drop. Nested `__kobako_dispatch`
|
|
88
|
-
/// frames stack within one Invocation
|
|
88
|
+
/// frames stack within one Invocation — the inner frame's `set`
|
|
89
89
|
/// swaps in its own pointer while remembering the outer's; drop
|
|
90
90
|
/// restores the outer so its continuation (e.g. iterating over another
|
|
91
91
|
/// guest block) still finds a live caller.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//! `Runtime::instantiate` resolves the ABI exports the run path drives
|
|
5
5
|
//! (`__kobako_eval` / `__kobako_run` / `__kobako_take_outcome` /
|
|
6
6
|
//! `__kobako_alloc`) plus the `memory` export against each fresh
|
|
7
|
-
//! per-invocation instance
|
|
7
|
+
//! per-invocation instance and bundles their
|
|
8
8
|
//! typed handles here, so the invocation body passes one struct around
|
|
9
9
|
//! rather than re-resolving exports by name at every step. Distinct
|
|
10
10
|
//! from `super::cache` (the process-wide Engine / Module cache): this
|
|
@@ -29,7 +29,7 @@ static INSTANCE_PRE_CACHE: OnceLock<Mutex<HashMap<PathBuf, InstancePre<Invocatio
|
|
|
29
29
|
/// Look up `path` in the per-path `InstancePre` cache, wiring the
|
|
30
30
|
/// Linker and resolving the Module's imports on a miss. Compilation
|
|
31
31
|
/// faults surface through `cached_module`; import-resolution faults
|
|
32
|
-
/// raise `Kobako::SetupError
|
|
32
|
+
/// raise `Kobako::SetupError`.
|
|
33
33
|
pub(crate) fn cached_instance_pre(path: &Path) -> Result<InstancePre<Invocation>, MagnusError> {
|
|
34
34
|
let cache = INSTANCE_PRE_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
|
35
35
|
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
//! for the lifetime of one `Runtime::eval` / `Runtime::run` call).
|
|
4
4
|
//!
|
|
5
5
|
//! Owned as the data of each per-invocation `wasmtime::Store`
|
|
6
|
-
//!
|
|
6
|
+
//! and threaded through every host import —
|
|
7
7
|
//! the `__kobako_dispatch` dispatcher reads the bound dispatch Proc,
|
|
8
8
|
//! while the run-path methods on `crate::runtime::Runtime` install the
|
|
9
|
-
//! invocation's WASI context + pipes at Store creation
|
|
10
|
-
//! (docs/behavior.md B-03 / B-04).
|
|
9
|
+
//! invocation's WASI context + pipes at Store creation.
|
|
11
10
|
//!
|
|
12
11
|
//! The slot also carries the per-invocation wall-clock deadline
|
|
13
|
-
//!
|
|
14
|
-
//! delta cap `MemoryLimiter
|
|
12
|
+
//! and the per-invocation linear-memory
|
|
13
|
+
//! delta cap `MemoryLimiter`. Both are
|
|
15
14
|
//! read from the wasmtime `epoch_deadline_callback` / `ResourceLimiter`
|
|
16
15
|
//! callbacks installed in `crate::runtime::Runtime::new_store`. The
|
|
17
16
|
//! memory cap measures only the `memory.grow` delta past the linear-
|
|
@@ -52,7 +51,7 @@ impl Invocation {
|
|
|
52
51
|
/// Build a fresh per-Store host state. `memory_limit` carries the
|
|
53
52
|
/// `Sandbox#memory_limit` cap in bytes (or `None` to disable the cap);
|
|
54
53
|
/// it is read from the wasmtime `ResourceLimiter` callback every
|
|
55
|
-
/// time the guest grows linear memory
|
|
54
|
+
/// time the guest grows linear memory.
|
|
56
55
|
pub(super) fn new(memory_limit: Option<usize>) -> Self {
|
|
57
56
|
Self {
|
|
58
57
|
wasi: None,
|
|
@@ -69,7 +68,7 @@ impl Invocation {
|
|
|
69
68
|
/// Install a freshly-built WASI context plus the matching stdout/stderr
|
|
70
69
|
/// pipe clones. Called from `crate::runtime::Runtime::eval` /
|
|
71
70
|
/// `crate::runtime::Runtime::run` at the top of every guest
|
|
72
|
-
/// invocation
|
|
71
|
+
/// invocation.
|
|
73
72
|
pub(super) fn install_wasi(
|
|
74
73
|
&mut self,
|
|
75
74
|
wasi: WasiP1Ctx,
|
|
@@ -127,7 +126,7 @@ impl Invocation {
|
|
|
127
126
|
|
|
128
127
|
/// Replace the per-run wall-clock deadline. `Some(at)` makes the
|
|
129
128
|
/// epoch-deadline callback trap once `Instant::now() >= at`; `None`
|
|
130
|
-
/// disables the cap. Called at the top of every `#run
|
|
129
|
+
/// disables the cap. Called at the top of every `#run`.
|
|
131
130
|
pub(super) fn set_deadline(&mut self, deadline: Option<Instant>) {
|
|
132
131
|
self.deadline = deadline;
|
|
133
132
|
}
|
|
@@ -149,7 +148,7 @@ impl Invocation {
|
|
|
149
148
|
&mut self.limiter
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
/// Arm the
|
|
151
|
+
/// Arm the memory cap for one guest run with
|
|
153
152
|
/// the current linear-memory size as the baseline. The limiter
|
|
154
153
|
/// charges only the `memory.grow` delta past `baseline` against
|
|
155
154
|
/// the cap, so the mruby image's initial allocation and the
|
|
@@ -162,23 +161,23 @@ impl Invocation {
|
|
|
162
161
|
self.limiter.activate(baseline);
|
|
163
162
|
}
|
|
164
163
|
|
|
165
|
-
/// Disarm the
|
|
164
|
+
/// Disarm the memory cap. See
|
|
166
165
|
/// `Invocation::arm_memory_cap`.
|
|
167
166
|
pub(super) fn disarm_memory_cap(&mut self) {
|
|
168
167
|
self.limiter.deactivate();
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
/// Stamp the wall-clock entry instant for the
|
|
172
|
-
///
|
|
170
|
+
/// Stamp the wall-clock entry instant for the `wall_time`
|
|
171
|
+
/// measurement. Called at the top of every
|
|
173
172
|
/// invocation immediately before the guest export call so the
|
|
174
|
-
/// bracket matches the `timeout` deadline accounting
|
|
173
|
+
/// bracket matches the `timeout` deadline accounting and
|
|
175
174
|
/// excludes post-run host bookkeeping such as `OUTCOME_BUFFER`
|
|
176
175
|
/// decoding.
|
|
177
176
|
pub(super) fn start_wall_clock(&mut self) {
|
|
178
177
|
self.wall_entry = Some(Instant::now());
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
/// Close the
|
|
180
|
+
/// Close the `wall_time` measurement
|
|
182
181
|
/// started by `Invocation::start_wall_clock`. Idempotent — a
|
|
183
182
|
/// stop with no matching start (e.g. if the guest export call
|
|
184
183
|
/// never executed because of a host-side allocation failure)
|
|
@@ -190,13 +189,13 @@ impl Invocation {
|
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
/// Return the wall-clock duration the most recent invocation
|
|
193
|
-
/// spent inside the guest export call
|
|
192
|
+
/// spent inside the guest export call.
|
|
194
193
|
/// Zero before the first invocation.
|
|
195
194
|
pub(super) fn wall_time(&self) -> Duration {
|
|
196
195
|
self.wall_time
|
|
197
196
|
}
|
|
198
197
|
|
|
199
|
-
/// Return the
|
|
198
|
+
/// Return the `memory_peak` — the high-
|
|
200
199
|
/// water mark of the per-invocation `memory.grow` delta past the
|
|
201
200
|
/// linear-memory size captured at invocation entry. Zero before
|
|
202
201
|
/// the first invocation.
|
|
@@ -206,7 +205,7 @@ impl Invocation {
|
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
/// Resource limiter that enforces the per-invocation `memory_limit`
|
|
209
|
-
/// cap
|
|
208
|
+
/// cap.
|
|
210
209
|
///
|
|
211
210
|
/// `max_memory` is the byte cap on per-invocation growth (`None` disables
|
|
212
211
|
/// the cap). `baseline` is the linear-memory size captured at invocation
|
|
@@ -249,9 +248,9 @@ impl MemoryLimiter {
|
|
|
249
248
|
/// the cap is dormant by default — the module's declared initial
|
|
250
249
|
/// memory is allocated during `Linker::instantiate` and the
|
|
251
250
|
/// per-invocation budget excludes anything that existed before
|
|
252
|
-
/// arming
|
|
251
|
+
/// arming. Also clears the
|
|
253
252
|
/// per-invocation `MemoryLimiter::peak` high-water so the
|
|
254
|
-
///
|
|
253
|
+
/// `memory_peak` accounting restarts from
|
|
255
254
|
/// zero for the new invocation.
|
|
256
255
|
fn activate(&mut self, baseline: usize) {
|
|
257
256
|
self.baseline = baseline;
|
|
@@ -270,9 +269,9 @@ impl MemoryLimiter {
|
|
|
270
269
|
/// Return the high-water mark of the per-invocation
|
|
271
270
|
/// `memory.grow` delta past `baseline` observed since the last
|
|
272
271
|
/// `MemoryLimiter::activate`. Read after the guest export
|
|
273
|
-
/// returns to populate `Kobako::Usage#memory_peak
|
|
274
|
-
///
|
|
275
|
-
/// rejected `desired` values that trip the
|
|
272
|
+
/// returns to populate `Kobako::Usage#memory_peak`.
|
|
273
|
+
/// Pinned to the last accepted grow —
|
|
274
|
+
/// rejected `desired` values that trip the memory
|
|
276
275
|
/// cap never update the peak, so the reported value never exceeds
|
|
277
276
|
/// `memory_limit`.
|
|
278
277
|
pub(super) fn peak(&self) -> usize {
|
|
@@ -312,8 +311,9 @@ impl ResourceLimiter for MemoryLimiter {
|
|
|
312
311
|
}
|
|
313
312
|
}
|
|
314
313
|
|
|
315
|
-
/// Marker error returned from `MemoryLimiter::memory_growing`
|
|
316
|
-
///
|
|
314
|
+
/// Marker error returned from `MemoryLimiter::memory_growing` when the
|
|
315
|
+
/// per-invocation memory cap is exceeded. Downcast from the wasmtime
|
|
316
|
+
/// trap error to surface as
|
|
317
317
|
/// `Kobako::MemoryLimitError` on the Ruby side. Callers use the
|
|
318
318
|
/// `Display` impl below — no field is read directly — so the inner
|
|
319
319
|
/// state stays private.
|
|
@@ -347,9 +347,9 @@ impl std::fmt::Display for MemoryLimitTrap {
|
|
|
347
347
|
|
|
348
348
|
impl std::error::Error for MemoryLimitTrap {}
|
|
349
349
|
|
|
350
|
-
/// Marker error returned from the epoch-deadline callback
|
|
351
|
-
///
|
|
352
|
-
/// surface as `Kobako::TimeoutError` on the Ruby side.
|
|
350
|
+
/// Marker error returned from the epoch-deadline callback when the
|
|
351
|
+
/// wall-clock deadline is exceeded. Downcast from the wasmtime trap
|
|
352
|
+
/// error to surface as `Kobako::TimeoutError` on the Ruby side.
|
|
353
353
|
#[derive(Debug)]
|
|
354
354
|
pub(crate) struct TimeoutTrap;
|
|
355
355
|
|
|
@@ -17,7 +17,7 @@ use super::invocation::{Invocation, MemoryLimitTrap, TimeoutTrap};
|
|
|
17
17
|
use super::{memory_limit_err, setup_err, timeout_err, trap_err};
|
|
18
18
|
|
|
19
19
|
/// Epoch-deadline callback installed on every Store. Read the per-run
|
|
20
|
-
/// wall-clock deadline from `Invocation`
|
|
20
|
+
/// wall-clock deadline from `Invocation` and trap with
|
|
21
21
|
/// `TimeoutTrap` once the deadline has passed; otherwise extend the
|
|
22
22
|
/// next check by one tick of the process-wide epoch ticker. When the
|
|
23
23
|
/// deadline is `None` the callback should not fire under normal
|
|
@@ -41,9 +41,9 @@ pub(super) fn epoch_deadline_callback(
|
|
|
41
41
|
/// without the magnus surface.
|
|
42
42
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
43
43
|
enum TrapClass {
|
|
44
|
-
///
|
|
44
|
+
/// Wall-clock cap path.
|
|
45
45
|
Timeout,
|
|
46
|
-
///
|
|
46
|
+
/// Linear-memory cap path.
|
|
47
47
|
MemoryLimit,
|
|
48
48
|
/// Any other wasmtime error — surfaces as the base
|
|
49
49
|
/// `Kobako::TrapError`.
|
|
@@ -117,8 +117,8 @@ fn other_trap_message(err: &wasmtime::Error) -> String {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/// Map an instantiation error to `Kobako::SetupError`. Instantiation runs
|
|
120
|
-
/// during `from_path` construction, before any invocation —
|
|
121
|
-
///
|
|
120
|
+
/// during `from_path` construction, before any invocation — every such
|
|
121
|
+
/// failure is a construction setup fault, not a
|
|
122
122
|
/// per-invocation cap outcome. The memory cap is dormant during
|
|
123
123
|
/// instantiation (see `Invocation::arm_memory_cap` /
|
|
124
124
|
/// `Invocation::disarm_memory_cap`) and the epoch deadline is not yet
|