kobako 0.18.0-aarch64-linux → 0.20.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +31 -0
- data/README.md +4 -1
- data/crates/kobako-runtime/CHANGELOG.md +14 -0
- data/crates/kobako-runtime/README.md +1 -1
- data/crates/kobako-wasmtime/CHANGELOG.md +14 -0
- data/crates/kobako-wasmtime/README.md +1 -1
- data/data/kobako.wasm +0 -0
- data/lib/kobako/3.3/kobako.so +0 -0
- data/lib/kobako/3.4/kobako.so +0 -0
- data/lib/kobako/4.0/kobako.so +0 -0
- data/lib/kobako/catalog/extensions.rb +50 -43
- data/lib/kobako/catalog/handles.rb +7 -16
- data/lib/kobako/catalog/services.rb +22 -36
- data/lib/kobako/catalog/snippets.rb +11 -10
- data/lib/kobako/context.rb +202 -0
- data/lib/kobako/errors.rb +26 -4
- data/lib/kobako/execution.rb +52 -0
- data/lib/kobako/extension.rb +24 -10
- data/lib/kobako/pool.rb +1 -7
- data/lib/kobako/sandbox.rb +43 -182
- data/lib/kobako/sandbox_options.rb +23 -2
- data/lib/kobako/transport/dispatcher.rb +16 -15
- data/lib/kobako/transport/yielder.rb +1 -1
- data/lib/kobako/unresolved.rb +17 -0
- data/lib/kobako/usage.rb +6 -6
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +1 -0
- data/release-please-config.json +2 -1
- data/sig/kobako/catalog/extensions.rbs +4 -6
- data/sig/kobako/catalog/handles.rbs +0 -2
- data/sig/kobako/catalog/services.rbs +3 -3
- data/sig/kobako/catalog/snippets.rbs +2 -0
- data/sig/kobako/context.rbs +36 -0
- data/sig/kobako/errors.rbs +12 -3
- data/sig/kobako/execution.rbs +24 -0
- data/sig/kobako/extension.rbs +4 -2
- data/sig/kobako/runtime.rbs +37 -9
- data/sig/kobako/sandbox.rbs +5 -24
- data/sig/kobako/sandbox_options.rbs +9 -2
- data/sig/kobako/transport/dispatcher.rbs +3 -3
- data/sig/kobako/unresolved.rbs +4 -0
- metadata +8 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "capture"
|
|
4
|
+
require_relative "codec"
|
|
5
|
+
require_relative "errors"
|
|
6
|
+
require_relative "execution"
|
|
7
|
+
require_relative "unresolved"
|
|
8
|
+
require_relative "outcome"
|
|
9
|
+
require_relative "usage"
|
|
10
|
+
require_relative "transport"
|
|
11
|
+
require_relative "catalog"
|
|
12
|
+
|
|
13
|
+
module Kobako
|
|
14
|
+
# Kobako::Context — the per-invocation driver behind a single +Sandbox#eval+
|
|
15
|
+
# / +#run+. Each Context owns a fresh +Catalog::Handles+ table and drives one
|
|
16
|
+
# guest invocation: it builds the dispatch handler, runs the guest, records
|
|
17
|
+
# the run's captures and usage, and decodes the outcome. One Context per
|
|
18
|
+
# invocation owns all per-invocation state, so the reusable +Sandbox+ holds
|
|
19
|
+
# none and overlapping evals never touch each other's state.
|
|
20
|
+
class Context
|
|
21
|
+
# Build a Context over the Sandbox-owned config — the +Runtime+, the
|
|
22
|
+
# sealed +Catalog::Services+ / +Catalog::Snippets+ registries, and the
|
|
23
|
+
# +Catalog::Extensions+ whose callable backends this Context resolves for
|
|
24
|
+
# its own run. The +Catalog::Handles+ table is this invocation's own, so
|
|
25
|
+
# guest→host dispatch and host→guest auto-wrap share one allocator scoped
|
|
26
|
+
# to the run; the resolved provider map is likewise per-invocation, so
|
|
27
|
+
# concurrent invocations never share mutable state.
|
|
28
|
+
def initialize(runtime:, services:, snippets:, extensions:)
|
|
29
|
+
@runtime = runtime
|
|
30
|
+
@services = services
|
|
31
|
+
@snippets = snippets
|
|
32
|
+
@extensions = extensions
|
|
33
|
+
@resolved = {} # : Hash[String, untyped]
|
|
34
|
+
@overrides = {} # : Hash[String, untyped]
|
|
35
|
+
@spent = false
|
|
36
|
+
@handler = Catalog::Handles.new
|
|
37
|
+
@stdout_capture = @stderr_capture = Capture::EMPTY
|
|
38
|
+
@usage = Usage::EMPTY
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Override the object bound at an already-declared +path+ for this
|
|
42
|
+
# invocation only — the per-eval hook the +#eval+ / +#run+ block uses to
|
|
43
|
+
# fill a fillable or shadow any static / per-invocation binding. +path+
|
|
44
|
+
# must name a declared (Frame 1) binding; overriding an undeclared path
|
|
45
|
+
# raises +ArgumentError+ so the Frame 1 key set stays fixed. Valid only
|
|
46
|
+
# while the block runs — the Context is spent once the block returns, so a
|
|
47
|
+
# captured +ctx+ used afterward raises +ArgumentError+ too, putting both
|
|
48
|
+
# misuses on one rescuable channel. Returns +self+.
|
|
49
|
+
def bind(path, object)
|
|
50
|
+
raise ArgumentError, "Kobako::Context is spent; ctx.bind is only valid inside the #eval / #run block" if @spent
|
|
51
|
+
|
|
52
|
+
key = path.to_s
|
|
53
|
+
raise ArgumentError, "cannot override undeclared path #{key.inspect}" unless @services.bound?(key)
|
|
54
|
+
|
|
55
|
+
@overrides[key] = object
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Resolve a Service +path+ to the object backing it this invocation,
|
|
60
|
+
# layering the per-eval +ctx.bind+ overrides over this Context's
|
|
61
|
+
# per-invocation provider results over the Sandbox's static base bindings.
|
|
62
|
+
# An unbound path raises +KeyError+; a fillable left unfilled resolves to
|
|
63
|
+
# +Kobako::Unresolved+ and is reported the same way, so an unresolved
|
|
64
|
+
# capability fails closed as an undefined target rather than dispatching to
|
|
65
|
+
# the sentinel. The Dispatcher maps either +KeyError+ to an
|
|
66
|
+
# undefined-target wire fault. Internal — the per-invocation dispatch
|
|
67
|
+
# handler is the sole caller.
|
|
68
|
+
def lookup(path)
|
|
69
|
+
key = path.to_s
|
|
70
|
+
object = @overrides.fetch(key) { @resolved.fetch(key) { @services.lookup(path) } }
|
|
71
|
+
raise KeyError, "service #{path} is declared but unresolved this invocation" if Unresolved.equal?(object)
|
|
72
|
+
|
|
73
|
+
object
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Execute a guest mruby source string in a fresh +mrb_state+ and return the
|
|
77
|
+
# decoded last expression. A given +block+ runs first, receiving this
|
|
78
|
+
# Context to collect +ctx.bind+ overrides before the guest drives.
|
|
79
|
+
def eval(code, &block)
|
|
80
|
+
collect_overrides(&block) if block
|
|
81
|
+
invoke!(:eval) do
|
|
82
|
+
@runtime.eval(dispatch_handler, @services.encode, code.b, @snippets.encode)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Dispatch a +Transport::Run+ envelope into a preloaded entrypoint and
|
|
87
|
+
# return the decoded result. A given +block+ runs first, receiving this
|
|
88
|
+
# Context to collect +ctx.bind+ overrides before the guest drives.
|
|
89
|
+
def run(run_envelope, &block)
|
|
90
|
+
collect_overrides(&block) if block
|
|
91
|
+
invoke!(:run) do
|
|
92
|
+
@runtime.run(dispatch_handler, @services.encode, @snippets.encode, run_envelope.encode(@handler))
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# Run the per-eval override +block+, handing it this Context so it can call
|
|
99
|
+
# +ctx.bind+, then spend the Context so a captured +ctx+ used after the
|
|
100
|
+
# block raises. A block that raises propagates before the guest drives, so
|
|
101
|
+
# the guest never runs and no Execution is produced.
|
|
102
|
+
def collect_overrides
|
|
103
|
+
yield self
|
|
104
|
+
ensure
|
|
105
|
+
@spent = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Build this invocation's guest→host dispatch handler — a +Proc+ routing
|
|
109
|
+
# each guest→host call through the stateless +Transport::Dispatcher+,
|
|
110
|
+
# capturing this Context as the path resolver (its +#lookup+ layers the
|
|
111
|
+
# per-invocation providers over the static bindings) plus +@handler+. Handed to
|
|
112
|
+
# +Runtime#eval+ / +#run+ as a call argument, so the Runtime holds no
|
|
113
|
+
# dispatch state and the +Proc+ stays GC-rooted as a live argument for the
|
|
114
|
+
# synchronous call. The ext hands the +Proc+ a per-dispatch +guest_yielder+
|
|
115
|
+
# — a +String → String+ callable that re-enters the in-flight guest to run
|
|
116
|
+
# a yielded block — which the +Dispatcher+ forwards to the
|
|
117
|
+
# +Transport::Yielder+ it builds for the call.
|
|
118
|
+
def dispatch_handler
|
|
119
|
+
lambda do |request_bytes, guest_yielder|
|
|
120
|
+
Transport::Dispatcher.dispatch(request_bytes, self, @handler, guest_yielder)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Record this invocation's usage and both output captures from the ext
|
|
125
|
+
# +Snapshot+. Every Snapshot carries them — value return or trap alike — so
|
|
126
|
+
# +#usage+ / +#stdout+ / +#stderr+ stay readable after a rescued trap.
|
|
127
|
+
def populate_observability!(snapshot)
|
|
128
|
+
@usage = Usage.new(wall_time: snapshot.wall_time, memory_peak: snapshot.memory_peak)
|
|
129
|
+
@stdout_capture = Capture.new(bytes: snapshot.stdout, truncated: snapshot.stdout_truncated?)
|
|
130
|
+
@stderr_capture = Capture.new(bytes: snapshot.stderr, truncated: snapshot.stderr_truncated?)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Pick the +TrapError+ subclass to re-raise based on +err+'s actual class.
|
|
134
|
+
# Cap-trap subclasses (+TimeoutError+ / +MemoryLimitError+) preserve their
|
|
135
|
+
# named identity; everything else collapses to the base +Kobako::TrapError+,
|
|
136
|
+
# so #invoke! can add the verb prefix without erasing the named subclass.
|
|
137
|
+
def trap_class_for(err)
|
|
138
|
+
case err
|
|
139
|
+
when TimeoutError then TimeoutError
|
|
140
|
+
when MemoryLimitError then MemoryLimitError
|
|
141
|
+
else TrapError
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Build the +TrapError+-family exception for a trapped +Snapshot+ from its
|
|
146
|
+
# neutral trap kind, tagged with the verb — the cap subclasses
|
|
147
|
+
# (+TimeoutError+ / +MemoryLimitError+) keep their identity, every other
|
|
148
|
+
# engine fault is the base +TrapError+.
|
|
149
|
+
def trap_error_for(snapshot, verb)
|
|
150
|
+
klass = case snapshot.trap_kind
|
|
151
|
+
when :timeout then TimeoutError
|
|
152
|
+
when :memory_limit then MemoryLimitError
|
|
153
|
+
else TrapError
|
|
154
|
+
end
|
|
155
|
+
klass.new("Sandbox##{verb} failed: #{snapshot.trap_message}")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Freeze this run's observables plus +value+ (+nil+ on a failed run) into
|
|
159
|
+
# the read-only +Execution+ the caller receives or the error carries.
|
|
160
|
+
# +failed+ records the two apart so a +nil+ +value+ from a successful run
|
|
161
|
+
# stays distinct from a failed one.
|
|
162
|
+
def build_execution(value, failed:)
|
|
163
|
+
Execution.new(value: value, usage: @usage, stdout: @stdout_capture, stderr: @stderr_capture, failed: failed)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Decode a completed run's outcome into its +Execution+. A Capability
|
|
167
|
+
# Handle in the result is restored to its host object first. Decode sits in
|
|
168
|
+
# the rescue so a wire-violation trap or a Panic envelope both attach this
|
|
169
|
+
# run's Execution, just like a guest-call trap does.
|
|
170
|
+
def settle_outcome(snapshot, verb)
|
|
171
|
+
value, carried = Codec.track_handles { Outcome.decode(snapshot.outcome) }
|
|
172
|
+
value = Codec::HandleWalk.deep_restore(value, @handler) if carried
|
|
173
|
+
build_execution(value, failed: false)
|
|
174
|
+
rescue Kobako::TrapError => e
|
|
175
|
+
raise trap_class_for(e).new("Sandbox##{verb} failed: #{e.message}").with_execution(build_execution(nil,
|
|
176
|
+
failed: true))
|
|
177
|
+
rescue Kobako::SandboxError, Kobako::ServiceError => e
|
|
178
|
+
raise e.with_execution(build_execution(nil, failed: true))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Drive one invocation and settle it into a frozen +Execution+. +verb+
|
|
182
|
+
# tags the TrapError message so the failing export is identifiable. This
|
|
183
|
+
# invocation's callable Extension backends are resolved first — before the
|
|
184
|
+
# guest runs — so a provider that raises propagates unwrapped and leaves
|
|
185
|
+
# the guest unrun. Usage and captures are recorded before the trap check,
|
|
186
|
+
# so a trapped Snapshot's error carries them just like a completed run's
|
|
187
|
+
# return value. A could-not-start fault ran no invocation at all, so it
|
|
188
|
+
# carries no Execution and gains only the verb prefix.
|
|
189
|
+
def invoke!(verb)
|
|
190
|
+
@resolved = @extensions.resolve
|
|
191
|
+
begin
|
|
192
|
+
snapshot = yield
|
|
193
|
+
rescue Kobako::TrapError => e
|
|
194
|
+
raise trap_class_for(e), "Sandbox##{verb} failed: #{e.message}"
|
|
195
|
+
end
|
|
196
|
+
populate_observability!(snapshot)
|
|
197
|
+
return settle_outcome(snapshot, verb) unless snapshot.trapped?
|
|
198
|
+
|
|
199
|
+
raise trap_error_for(snapshot, verb).with_execution(build_execution(nil, failed: true))
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
data/lib/kobako/errors.rb
CHANGED
|
@@ -16,6 +16,24 @@ module Kobako
|
|
|
16
16
|
# taxonomy can rescue a single class.
|
|
17
17
|
class Error < StandardError; end
|
|
18
18
|
|
|
19
|
+
# Carries the frozen +Kobako::Execution+ of the run that failed, so a
|
|
20
|
+
# rescue reads its captures and usage exactly as a successful caller reads
|
|
21
|
+
# them off the return value. Mixed only into the three invocation-outcome
|
|
22
|
+
# classes (+TrapError+ / +SandboxError+ / +ServiceError+ and their
|
|
23
|
+
# subclasses); +SetupError+ and +PoolTimeoutError+ arise outside any
|
|
24
|
+
# invocation and carry no Execution. Defaults to +nil+ — a pre-flight
|
|
25
|
+
# failure that ran no invocation leaves it unset.
|
|
26
|
+
module CarriesExecution
|
|
27
|
+
attr_reader :execution
|
|
28
|
+
|
|
29
|
+
# Attach the failed run's +Kobako::Execution+ and return +self+ so the
|
|
30
|
+
# raise site reads +raise error.with_execution(execution)+.
|
|
31
|
+
def with_execution(execution)
|
|
32
|
+
@execution = execution
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
19
37
|
# Wasm engine layer. Raised when the Wasm execution engine crashed
|
|
20
38
|
# (trap, OOM, unreachable) or when the wire layer detected a structural
|
|
21
39
|
# violation that signals a corrupted guest execution environment
|
|
@@ -31,7 +49,9 @@ module Kobako
|
|
|
31
49
|
# Sandbox" can rescue +TrapError+ and ignore the subclass; Host Apps that
|
|
32
50
|
# want to surface a specific reason to operators can rescue the subclass
|
|
33
51
|
# first.
|
|
34
|
-
class TrapError < Error
|
|
52
|
+
class TrapError < Error
|
|
53
|
+
include CarriesExecution
|
|
54
|
+
end
|
|
35
55
|
|
|
36
56
|
# Wall-clock timeout cap exhausted: the absolute deadline
|
|
37
57
|
# +entry_time + timeout+ passed and the next guest wasm safepoint
|
|
@@ -70,7 +90,7 @@ module Kobako
|
|
|
70
90
|
# beyond its message. Mixed into both rather than promoted to a shared
|
|
71
91
|
# superclass because +SandboxError+ and +ServiceError+ sit in distinct
|
|
72
92
|
# branches of the invocation-outcome taxonomy under +Kobako::Error+.
|
|
73
|
-
module
|
|
93
|
+
module Diagnosable
|
|
74
94
|
attr_reader :origin, :klass, :backtrace_lines, :details
|
|
75
95
|
|
|
76
96
|
def initialize(message, origin: nil, klass: nil, backtrace_lines: nil, details: nil)
|
|
@@ -86,14 +106,16 @@ module Kobako
|
|
|
86
106
|
# execution failed due to a mruby script error, a protocol fault, or a
|
|
87
107
|
# host-side wire decode failure on an otherwise valid outcome tag.
|
|
88
108
|
class SandboxError < Error
|
|
89
|
-
include
|
|
109
|
+
include Diagnosable
|
|
110
|
+
include CarriesExecution
|
|
90
111
|
end
|
|
91
112
|
|
|
92
113
|
# Service layer. Raised when a Service capability call inside a mruby
|
|
93
114
|
# script reported an application-level failure that the script did not
|
|
94
115
|
# rescue.
|
|
95
116
|
class ServiceError < Error
|
|
96
|
-
include
|
|
117
|
+
include Diagnosable
|
|
118
|
+
include CarriesExecution
|
|
97
119
|
end
|
|
98
120
|
|
|
99
121
|
# HandleExhaustedError is the canonical SandboxError subclass for the
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "capture"
|
|
4
|
+
require_relative "usage"
|
|
5
|
+
|
|
6
|
+
module Kobako
|
|
7
|
+
# Kobako::Execution — the frozen result of one +Sandbox#eval+ / +#run+: the
|
|
8
|
+
# decoded +#value+ plus the run's output captures and +#usage+. A successful
|
|
9
|
+
# run returns it; a failed run raises an error carrying the same frozen
|
|
10
|
+
# Execution on the error's +#execution+, so a rescue reads the captures and
|
|
11
|
+
# usage exactly as a successful caller reads them off the return value. On a
|
|
12
|
+
# failed run +#value+ is +nil+ — only the captures and usage are meaningful.
|
|
13
|
+
#
|
|
14
|
+
# +#failed?+ tells the returned Execution from the carried one even when both
|
|
15
|
+
# +#value+ are +nil+ (a run whose last expression was +nil+ versus a failed
|
|
16
|
+
# one). It holds no reference to the raising error, so the error carries the
|
|
17
|
+
# Execution but not the reverse.
|
|
18
|
+
class Execution
|
|
19
|
+
# The deserialized guest value the run produced; +nil+ on a failed run.
|
|
20
|
+
attr_reader :value
|
|
21
|
+
|
|
22
|
+
# The +Kobako::Usage+ resource accounting for this run.
|
|
23
|
+
attr_reader :usage
|
|
24
|
+
|
|
25
|
+
def initialize(value:, usage:, stdout:, stderr:, failed:)
|
|
26
|
+
@value = value
|
|
27
|
+
@usage = usage
|
|
28
|
+
@stdout_capture = stdout
|
|
29
|
+
@stderr_capture = stderr
|
|
30
|
+
@failed = failed
|
|
31
|
+
freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns +true+ iff the run failed — +false+ on the Execution +#eval+ /
|
|
35
|
+
# +#run+ returned, +true+ on the one a raised error carries.
|
|
36
|
+
def failed? = @failed
|
|
37
|
+
|
|
38
|
+
# Bytes the guest wrote to stdout during this run as a UTF-8 String,
|
|
39
|
+
# clipped at +stdout_limit+; the content carries no truncation sentinel,
|
|
40
|
+
# so use +#stdout_truncated?+ to observe overflow.
|
|
41
|
+
def stdout = @stdout_capture.bytes
|
|
42
|
+
|
|
43
|
+
# Bytes the guest wrote to stderr during this run. Mirror of #stdout.
|
|
44
|
+
def stderr = @stderr_capture.bytes
|
|
45
|
+
|
|
46
|
+
# Returns +true+ iff stdout capture reached +stdout_limit+ during this run.
|
|
47
|
+
def stdout_truncated? = @stdout_capture.truncated?
|
|
48
|
+
|
|
49
|
+
# Returns +true+ iff stderr capture reached +stderr_limit+ during this run.
|
|
50
|
+
def stderr_truncated? = @stderr_capture.truncated?
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/kobako/extension.rb
CHANGED
|
@@ -27,17 +27,31 @@ module Kobako
|
|
|
27
27
|
# Kobako::Extension::Backend — the host attachment of an Extension,
|
|
28
28
|
# pairing +path+ (the constant path the backend binds at, single-segment
|
|
29
29
|
# +"File"+ or nested +"MyApp::Store"+, spelling the guest constant the
|
|
30
|
-
# idiom routes to) with
|
|
30
|
+
# idiom routes to) with the source of the bound object, declared by
|
|
31
|
+
# explicit keyword:
|
|
31
32
|
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
|
|
33
|
+
# * +object:+ — a static object, fixed for the Sandbox's life.
|
|
34
|
+
# * +provider:+ — a no-argument callable invoked once per invocation to
|
|
35
|
+
# yield that invocation's object, so a fresh object backs the path
|
|
36
|
+
# every invocation. A provider that raises propagates its exception to
|
|
37
|
+
# the invocation caller and leaves the guest unrun; the next
|
|
38
|
+
# invocation resolves it afresh.
|
|
39
|
+
# * neither — a fillable, defaulting to +Kobako::Unresolved+ until the
|
|
40
|
+
# host supplies the invocation's object.
|
|
41
|
+
#
|
|
42
|
+
# The kind is chosen by keyword, never inferred from whether the value is
|
|
43
|
+
# callable, so a static object that is itself callable is expressed
|
|
44
|
+
# directly with +object:+. Giving both +object:+ and +provider:+ raises
|
|
45
|
+
# +ArgumentError+.
|
|
46
|
+
class Backend < Data.define(:path, :object, :provider)
|
|
47
|
+
def initialize(path:, object: nil, provider: nil)
|
|
48
|
+
if !object.nil? && !provider.nil?
|
|
49
|
+
raise ArgumentError,
|
|
50
|
+
"Extension::Backend accepts object: or provider:, not both"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
end
|
|
41
55
|
end
|
|
42
56
|
|
|
43
57
|
# +backend+ and +depends_on+ default to absent so the common
|
data/lib/kobako/pool.rb
CHANGED
|
@@ -47,7 +47,7 @@ module Kobako
|
|
|
47
47
|
# discarded and its slot refills by a fresh construction on next
|
|
48
48
|
# demand.
|
|
49
49
|
def with
|
|
50
|
-
sandbox =
|
|
50
|
+
sandbox = acquire
|
|
51
51
|
begin
|
|
52
52
|
yield sandbox
|
|
53
53
|
rescue TrapError
|
|
@@ -61,12 +61,6 @@ module Kobako
|
|
|
61
61
|
|
|
62
62
|
private
|
|
63
63
|
|
|
64
|
-
# Acquire a Sandbox and hand it over in pre-invocation state — empty
|
|
65
|
-
# output buffers and truncation predicates false.
|
|
66
|
-
def checkout
|
|
67
|
-
acquire.tap(&:reset_invocation_state!)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
64
|
# The idle-first claim loop: an idle Sandbox wins, unclaimed
|
|
71
65
|
# capacity constructs, and a full pool waits for a checkin.
|
|
72
66
|
def acquire
|