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
data/lib/kobako/codec/utils.rb
CHANGED
|
@@ -19,8 +19,7 @@ module Kobako
|
|
|
19
19
|
# - Representability predicate ({representable?}) and the symmetric
|
|
20
20
|
# host→guest +#run+ argument walk ({deep_wrap}) used by
|
|
21
21
|
# +Kobako::Transport::Run#encode+ to route non-representable leaves
|
|
22
|
-
# through the Sandbox's +Kobako::Catalog::Handles
|
|
23
|
-
# ({docs/behavior.md B-34}[link:../../../docs/behavior.md]).
|
|
22
|
+
# through the Sandbox's +Kobako::Catalog::Handles+.
|
|
24
23
|
#
|
|
25
24
|
# All helpers are pure — they only inspect inputs, never mutate
|
|
26
25
|
# them — except {deep_wrap}, whose only side effect is allocating
|
|
@@ -84,8 +83,7 @@ module Kobako
|
|
|
84
83
|
|
|
85
84
|
# Deep-walk Array / Hash containers in +value+ and replace every
|
|
86
85
|
# leaf that fails {representable?} with a +Kobako::Handle+
|
|
87
|
-
# allocated from +handler
|
|
88
|
-
# ({docs/behavior.md B-34}[link:../../../docs/behavior.md]). The
|
|
86
|
+
# allocated from +handler+. The
|
|
89
87
|
# walk only descends through representable container shapes
|
|
90
88
|
# (Array, Hash) one structural level at a time; a non-representable
|
|
91
89
|
# leaf is wrapped as-is without inspecting its internal structure.
|
|
@@ -116,8 +114,7 @@ module Kobako
|
|
|
116
114
|
|
|
117
115
|
# Deep-walk Array / Hash containers in +value+ and replace every
|
|
118
116
|
# +Kobako::Handle+ leaf with the host-side object +handler+ resolves
|
|
119
|
-
# it to
|
|
120
|
-
# The symmetric inverse of {deep_wrap}: that walk allocates objects
|
|
117
|
+
# it to. The symmetric inverse of {deep_wrap}: that walk allocates objects
|
|
121
118
|
# into Handles on the host→guest argument path; this walk resolves
|
|
122
119
|
# Handles back to their objects on every guest→host value path — the
|
|
123
120
|
# +#eval+ / +#run+ result and the yield-block result alike. The walk
|
|
@@ -126,11 +123,11 @@ module Kobako
|
|
|
126
123
|
# unchanged.
|
|
127
124
|
#
|
|
128
125
|
# +value+ is a decoded Ruby value (a Handle here is a wire-decoded
|
|
129
|
-
# +Kobako::Handle+, never a guest-forged one
|
|
126
|
+
# +Kobako::Handle+, never a guest-forged one); +handler+ must
|
|
130
127
|
# respond to +#fetch(id) -> object+ (a host-side
|
|
131
128
|
# +Kobako::Catalog::Handles+). +handler.fetch+ raises
|
|
132
|
-
# +Kobako::SandboxError+ for an id with no live binding,
|
|
133
|
-
# corrupted-runtime fallback
|
|
129
|
+
# +Kobako::SandboxError+ for an id with no live binding, the
|
|
130
|
+
# corrupted-runtime fallback.
|
|
134
131
|
def deep_restore(value, handler)
|
|
135
132
|
case value
|
|
136
133
|
when ::Array then value.map { |element| Utils.deep_restore(element, handler) }
|
data/lib/kobako/errors.rb
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# Top-level Kobako namespace.
|
|
4
4
|
module Kobako
|
|
5
|
-
# Error taxonomy
|
|
5
|
+
# Error taxonomy.
|
|
6
6
|
#
|
|
7
7
|
# Every `Kobako::Sandbox` invocation (`#eval` or `#run`) either returns a value or raises
|
|
8
8
|
# exactly one of three invocation-outcome classes. Attribution is decided after the
|
|
9
|
-
# guest binary returns control to the host
|
|
10
|
-
#
|
|
9
|
+
# guest binary returns control to the host: first the Wasm-trap layer, then
|
|
10
|
+
# the outcome-envelope tag.
|
|
11
11
|
#
|
|
12
12
|
# Three invocation-outcome branches:
|
|
13
13
|
#
|
|
@@ -25,19 +25,17 @@ module Kobako
|
|
|
25
25
|
#
|
|
26
26
|
# * {SetupError} — construction layer. Raised by `Kobako::Sandbox.new`
|
|
27
27
|
# when the wasm runtime cannot be built from the
|
|
28
|
-
# configured +wasm_path+ before any invocation runs
|
|
29
|
-
# ({docs/behavior.md E-40 / E-41}[link:../../docs/behavior.md]).
|
|
28
|
+
# configured +wasm_path+ before any invocation runs.
|
|
30
29
|
# Not an invocation outcome, so it never passes
|
|
31
30
|
# through the two-step attribution decision.
|
|
32
31
|
# * {PoolTimeoutError} — pool checkout layer. Raised by `Kobako::Pool#with`
|
|
33
|
-
# when the checkout wait exceeds +checkout_timeout
|
|
34
|
-
# ({docs/behavior.md E-46}[link:../../docs/behavior.md]).
|
|
32
|
+
# when the checkout wait exceeds +checkout_timeout+.
|
|
35
33
|
#
|
|
36
|
-
#
|
|
34
|
+
# Named subclasses:
|
|
37
35
|
#
|
|
38
36
|
# * {ModuleNotBuiltError} < {SetupError} — Guest Binary artifact absent
|
|
39
|
-
# at +wasm_path
|
|
40
|
-
# * {HandlerExhaustedError} < {SandboxError} — Handle id cap hit
|
|
37
|
+
# at +wasm_path+.
|
|
38
|
+
# * {HandlerExhaustedError} < {SandboxError} — Handle id cap hit.
|
|
41
39
|
|
|
42
40
|
# Base for all kobako-raised errors so callers that want to ignore the
|
|
43
41
|
# taxonomy can rescue a single class.
|
|
@@ -46,13 +44,13 @@ module Kobako
|
|
|
46
44
|
# Wasm engine layer. Raised when the Wasm execution engine crashed
|
|
47
45
|
# (trap, OOM, unreachable) or when the wire layer detected a structural
|
|
48
46
|
# violation that signals a corrupted guest execution environment
|
|
49
|
-
# (zero-length OUTCOME_BUFFER, unknown outcome tag
|
|
47
|
+
# (zero-length OUTCOME_BUFFER, unknown outcome tag).
|
|
50
48
|
#
|
|
51
|
-
# Two named subclasses cover the configured per-invocation caps
|
|
49
|
+
# Two named subclasses cover the configured per-invocation caps:
|
|
52
50
|
#
|
|
53
|
-
# * {TimeoutError} — wall-clock +timeout+ exceeded
|
|
51
|
+
# * {TimeoutError} — wall-clock +timeout+ exceeded.
|
|
54
52
|
# * {MemoryLimitError} — guest +memory.grow+ would exceed
|
|
55
|
-
# +memory_limit
|
|
53
|
+
# +memory_limit+.
|
|
56
54
|
#
|
|
57
55
|
# Host Apps that only care about "guest is unrecoverable, discard the
|
|
58
56
|
# Sandbox" can rescue +TrapError+ and ignore the subclass; Host Apps that
|
|
@@ -60,24 +58,23 @@ module Kobako
|
|
|
60
58
|
# first.
|
|
61
59
|
class TrapError < Error; end
|
|
62
60
|
|
|
63
|
-
# Wall-clock timeout cap exhausted
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
61
|
+
# Wall-clock timeout cap exhausted: the absolute deadline
|
|
62
|
+
# +entry_time + timeout+ passed and the next guest wasm safepoint
|
|
63
|
+
# trapped. The Sandbox is unrecoverable after this point; discard and
|
|
64
|
+
# recreate before another execution.
|
|
67
65
|
class TimeoutError < TrapError; end
|
|
68
66
|
|
|
69
|
-
# Linear-memory cap exhausted
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
67
|
+
# Linear-memory cap exhausted: a guest +memory.grow+ would have pushed
|
|
68
|
+
# linear memory past the configured +memory_limit+. The Sandbox is
|
|
69
|
+
# unrecoverable after this point; discard and recreate before another
|
|
70
|
+
# execution.
|
|
73
71
|
class MemoryLimitError < TrapError; end
|
|
74
72
|
|
|
75
73
|
# Construction-layer error raised by +Kobako::Sandbox.new+ /
|
|
76
74
|
# +Kobako::Runtime.from_path+ when the wasm runtime cannot be built
|
|
77
75
|
# from the configured +wasm_path+ before any invocation runs —
|
|
78
76
|
# an unreadable artifact, bytes that are not a valid Wasm module, or
|
|
79
|
-
# engine / linker / instantiation setup failure
|
|
80
|
-
# ({docs/behavior.md E-41}[link:../../docs/behavior.md]). Construction
|
|
77
|
+
# engine / linker / instantiation setup failure. Construction
|
|
81
78
|
# is not an invocation, so +SetupError+ sits beside the invocation
|
|
82
79
|
# taxonomy under +Kobako::Error+ rather than under +TrapError+: no
|
|
83
80
|
# Sandbox is produced, so the +TrapError+ "discard and recreate"
|
|
@@ -86,8 +83,7 @@ module Kobako
|
|
|
86
83
|
|
|
87
84
|
# The named +SetupError+ subclass for the common, actionable case:
|
|
88
85
|
# the Guest Binary artifact is absent at +wasm_path+ — the pre-build
|
|
89
|
-
# state on a fresh clone before +bundle exec rake compile
|
|
90
|
-
# ({docs/behavior.md E-40}[link:../../docs/behavior.md]). Host Apps
|
|
86
|
+
# state on a fresh clone before +bundle exec rake compile+. Host Apps
|
|
91
87
|
# that only need "the Sandbox could not be set up" rescue +SetupError+;
|
|
92
88
|
# those wanting to special-case the unbuilt-artifact state rescue
|
|
93
89
|
# +ModuleNotBuiltError+ first.
|
|
@@ -123,19 +119,18 @@ module Kobako
|
|
|
123
119
|
end
|
|
124
120
|
end
|
|
125
121
|
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
122
|
+
# HandlerExhaustedError is the canonical SandboxError subclass for the
|
|
123
|
+
# id-cap-hit path. Raised when the per-invocation Handle ID counter in
|
|
124
|
+
# Catalog::Handles reaches +0x7fff_ffff+ (2³¹ − 1) and further
|
|
125
|
+
# allocation would exceed the cap.
|
|
130
126
|
class HandlerExhaustedError < SandboxError; end
|
|
131
127
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
# preserved. Inherits from SandboxError so a single
|
|
128
|
+
# BytecodeError is the SandboxError subclass raised when a
|
|
129
|
+
# `#preload(binary:)` snippet fails structural validation during the
|
|
130
|
+
# first invocation's snippet replay against a fresh `mrb_state` (RITE
|
|
131
|
+
# version mismatch or corrupt body). Bytecode that loads cleanly and
|
|
132
|
+
# then raises at top level surfaces as plain `SandboxError` with the
|
|
133
|
+
# natural mruby class preserved. Inherits from SandboxError so a single
|
|
139
134
|
# `rescue Kobako::SandboxError` covers both source and bytecode
|
|
140
135
|
# snippet failures while callers wanting bytecode-specific handling
|
|
141
136
|
# can `rescue Kobako::BytecodeError` directly.
|
|
@@ -143,8 +138,7 @@ module Kobako
|
|
|
143
138
|
|
|
144
139
|
# Pool checkout layer. Raised by +Kobako::Pool#with+ when the checkout
|
|
145
140
|
# wait exceeded the configured +checkout_timeout+ while every slot was
|
|
146
|
-
# held
|
|
147
|
-
# Sandbox state is touched — retrying succeeds as soon as a holder
|
|
141
|
+
# held. No Sandbox state is touched — retrying succeeds as soon as a holder
|
|
148
142
|
# returns its Sandbox.
|
|
149
143
|
class PoolTimeoutError < Error; end
|
|
150
144
|
end
|
data/lib/kobako/handle.rb
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
module Kobako
|
|
4
4
|
# Wire-level value object for an ext-0x01 Capability Handle, used in both
|
|
5
5
|
# directions across the Sandbox boundary: as a Service method's return
|
|
6
|
-
# value (guest→host return path
|
|
7
|
-
#
|
|
8
|
-
# ({docs/behavior.md B-34}[link:../../docs/behavior.md]).
|
|
6
|
+
# value (guest→host return path) and as a +#run+ argument auto-wrapped
|
|
7
|
+
# by the host.
|
|
9
8
|
#
|
|
10
9
|
# SPEC pins the binary layout to fixext 4 with a 4-byte big-endian u32
|
|
11
10
|
# payload ({docs/wire-codec.md}[link:../../docs/wire-codec.md]
|
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)
|