Class: Kobako::Transport::Yielder
- Inherits:
-
Object
- Object
- Kobako::Transport::Yielder
- Defined in:
- lib/kobako/transport/yielder.rb,
sig/kobako/transport/yielder.rbs
Overview
Host-side stand-in for a guest-supplied block.
Each guest call that carries block_given: true gets a Yielder
that the Dispatcher hands to the Service method as &block. The
Service method observes it as an ordinary Ruby Proc through
#to_proc; yield val / block.call(val) invokes #yield, which
serialises the positional args, re-enters the guest via the injected
yield_to_guest lambda, and reifies the Yield Reply into Ruby
control flow:
* ok — return the decoded value to +yield+'s caller
* break — +throw break_tag, value+ so the Dispatcher's +catch+
frame unwinds the Service method
* error — raise a BlockError at the Service's yield site,
carrying the guest's class on +#klass+
The Dispatcher calls #invalidate! from its ensure block once
dispatch completes; any later call to a stashed Yielder then raises
LocalJumpError — the observable shape of an escaped Yielder.
Instance Method Summary collapse
-
#decode_body(body) ⇒ [untyped, bool]
Decode a value-carrying arm's payload, answering the value and whether the decode carried a Capability Handle.
-
#encode_args(args) ⇒ String
Encode what the Service is yielding.
-
#fault_text(error) ⇒ String?
The text the guest's own block failure crosses back as, or
nilwhenerroris not the BlockError this Yielder raised. -
#initialize(yield_to_guest, break_tag, handler) ⇒ Yielder
constructor
yield_to_guestis the ext's per-dispatchKobako::Runtime::GuestYielder, which #yield invokes to re-enter the guest: it takes the argument payload and answers the reply already split into[arm, body, class]. -
#invalidate! ⇒ void
Mark this Yielder dead.
-
#remember(error) ⇒ Kobako::BlockError
Hold onto the BlockError being raised so #fault_text can recognise it if it comes back unrescued, and return it so the raise site reads as one statement.
-
#restore(value, carried_handle) ⇒ Object
Restore any Capability Handle in a block's ok value to its host object via the injected
Catalog::Handles. -
#to_proc ⇒ Proc
The Proc the Dispatcher passes as
&block, binding #yield so a Service method'syield/block.calldrives the round-trip. -
#yield(*args) ⇒ Object
Re-enter the guest with
argsand reify the Yield Reply into Ruby control flow.
Constructor Details
#initialize(yield_to_guest, break_tag, handler) ⇒ Yielder
yield_to_guest is the ext's per-dispatch
Kobako::Runtime::GuestYielder, which #yield invokes to re-enter
the guest: it takes the argument payload and answers the reply
already split into [arm, body, class]. break_tag is the
catch throw tag the Dispatcher matches against to unwind the
Service on a break. handler is the invocation's Kobako::Catalog::Handles,
used to restore a Capability Handle in the block's ok value back to
its host object before it reaches the Service yield site.
39 40 41 42 43 44 45 |
# File 'lib/kobako/transport/yielder.rb', line 39 def initialize(yield_to_guest, break_tag, handler) @yield_to_guest = yield_to_guest @break_tag = break_tag @handler = handler @active = true @raised = nil end |
Instance Method Details
#decode_body(body) ⇒ [untyped, bool]
Decode a value-carrying arm's payload, answering the value and whether the decode carried a Capability Handle. The tracking bracket opens only around this decode: the guest re-entry may run nested dispatches whose own brackets would otherwise pollute the signal.
120 121 122 |
# File 'lib/kobako/transport/yielder.rb', line 120 def decode_body(body) Kobako::Codec.track_handles { Kobako::Codec::Decoder.decode(body) } end |
#encode_args(args) ⇒ String
Encode what the Service is yielding. A value outside the wire type set fails here, before the guest is re-entered, and is restated so the Service reads a refusal of its own argument rather than a codec class it never named.
101 102 103 104 105 |
# File 'lib/kobako/transport/yielder.rb', line 101 def encode_args(args) Kobako::Codec::Encoder.encode(args) rescue Kobako::Codec::Error => e raise YieldValueError, "Service yielded a value the block cannot receive: #{e.}" end |
#fault_text(error) ⇒ String?
The text the guest's own block failure crosses back as, or nil
when error is not the BlockError this Yielder raised. Identity
rather than class: a Service that rescued the block's failure and
raised its own has reported something else, and the guest must
hear about that instead.
The class travels with the message because not every block failure is an exception the guest holds — a block value the guest refused has a class to raise under and no object to continue.
56 57 58 59 60 61 |
# File 'lib/kobako/transport/yielder.rb', line 56 def fault_text(error) raised = @raised return if raised.nil? || !error.equal?(raised) "#{raised.klass}: #{raised.}" end |
#invalidate! ⇒ void
This method returns an undefined value.
Mark this Yielder dead. Called by the Dispatcher's ensure block
when the originating dispatch frame returns; any later #yield
call then raises LocalJumpError.
91 92 93 |
# File 'lib/kobako/transport/yielder.rb', line 91 def invalidate! @active = false end |
#remember(error) ⇒ Kobako::BlockError
Hold onto the BlockError being raised so #fault_text can recognise it if it comes back unrescued, and return it so the raise site reads as one statement. Only the newest is kept: a Service that rescued an earlier one and yielded again has already handled it.
111 112 113 |
# File 'lib/kobako/transport/yielder.rb', line 111 def remember(error) @raised = error end |
#restore(value, carried_handle) ⇒ Object
Restore any Capability Handle in a block's ok value to its host
object via the injected Catalog::Handles. Only the
ok path calls this — host code consumes the ok value, whereas a
break value returns to the guest and stays a Handle. A response
whose decode carried no Handle resolves to itself, so the walk is
skipped entirely.
130 131 132 133 134 |
# File 'lib/kobako/transport/yielder.rb', line 130 def restore(value, carried_handle) return value unless carried_handle Kobako::Codec::HandleWalk.deep_restore(value, @handler) end |
#to_proc ⇒ Proc
The Proc the Dispatcher passes as &block, binding #yield so a
Service method's yield / block.call drives the round-trip.
84 85 86 |
# File 'lib/kobako/transport/yielder.rb', line 84 def to_proc method(:yield).to_proc end |
#yield(*args) ⇒ Object
Re-enter the guest with args and reify the Yield Reply into
Ruby control flow. Raises LocalJumpError if called after
#invalidate!. The ok value is consumed by the host Service
method, so a Capability Handle in it is restored to its host object.
The break value unwinds past the Service back to the guest
bound-constant call, so it passes through verbatim — a Handle stays a
Handle and rides back on the same id rather than churning a new one.
70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/kobako/transport/yielder.rb', line 70 def yield(*args) raise LocalJumpError, "guest block invoked after host dispatch frame returned" unless @active arm, body, klass = @yield_to_guest.call(encode_args(args)) raise remember(BlockError.new(body, klass: klass)) if arm == :error value, carried_handle = decode_body(body) throw @break_tag, value if arm == :break restore(value, carried_handle) end |