Class: Kobako::RPC::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/kobako/rpc/server.rb

Overview

Kobako::RPC::Server — per-Sandbox host-side RPC coordinator. Maintains the Namespace / Member registry, owns the HandleTable, and routes incoming Requests to the resolved Service object (SPEC.md B-07..B-21).

Public API:

server = Kobako::RPC::Server.new
namespace = server.define(:MyService)    # => Kobako::RPC::Namespace
namespace.bind(:KV, kv_object)           # => namespace (chainable)
server.to_preamble                       # => array for Frame 1
server.dispatch(request_bytes)           # => msgpack bytes (delegated to Dispatcher)

Namespaces live at Kobako::RPC::Namespace (lib/kobako/rpc/namespace.rb). The opaque Handle allocator lives at Kobako::RPC::HandleTable (lib/kobako/rpc/handle_table.rb). Dispatch helpers live at Kobako::RPC::Dispatcher (lib/kobako/rpc/dispatcher.rb).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handle_table: HandleTable.new) ⇒ Server

Build a fresh Server. handle_table is an internal seam that injects a pre-configured HandleTable; tests pass one whose next_id is pinned near MAX_ID to exercise the B-21 cap-exhaustion path without 2³¹ allocations. Production callers leave it at the default.



36
37
38
39
40
# File 'lib/kobako/rpc/server.rb', line 36

def initialize(handle_table: HandleTable.new)
  @namespaces = {} # : Hash[String, Kobako::RPC::Namespace]
  @handle_table = handle_table
  @sealed = false
end

Instance Attribute Details

#handle_tableObject (readonly)

Expose the HandleTable for tests and wire-layer Handle wrapping.



142
143
144
# File 'lib/kobako/rpc/server.rb', line 142

def handle_table
  @handle_table
end

Instance Method Details

#bound?(target) ⇒ Boolean

Returns true when target (a “Namespace::Member” path) resolves to a bound member, false otherwise.

Returns:

  • (Boolean)


74
75
76
77
# File 'lib/kobako/rpc/server.rb', line 74

def bound?(target)
  namespace, member_name, = parse_target(target)
  !namespace.nil? && !member_name.nil? && !namespace[member_name].nil?
end

#define(name) ⇒ Object

Declare or retrieve the Namespace named name (idempotent — SPEC.md B-10). name is a constant-form name as a Symbol or String (must satisfy Namespace::NAME_PATTERN). Returns the Kobako::RPC::Namespace for that name, creating it if it does not exist. Raises ArgumentError when name is malformed, or when called after the owning Sandbox has been sealed by #run.

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
57
58
# File 'lib/kobako/rpc/server.rb', line 48

def define(name)
  raise ArgumentError, "cannot define after Sandbox#run has been invoked" if @sealed

  name_str = name.to_s
  unless Namespace::NAME_PATTERN.match?(name_str)
    raise ArgumentError,
          "Namespace name must match #{Namespace::NAME_PATTERN.inspect} (got #{name.inspect})"
  end

  @namespaces[name_str] ||= Namespace.new(name_str)
end

#dispatch(request_bytes) ⇒ Object

Dispatch a single RPC request and return the encoded response bytes (SPEC.md B-12). request_bytes is a msgpack-encoded Request envelope. Called by the Rust ext from inside __kobako_dispatch. Always returns a binary String — never raises. Delegates to Dispatcher.dispatch which reifies any failure as a Response.error envelope so the guest sees the failure as a normal RPC error rather than a wasm trap.



137
138
139
# File 'lib/kobako/rpc/server.rb', line 137

def dispatch(request_bytes)
  Dispatcher.dispatch(request_bytes, self)
end

#empty?Boolean

Returns true when no namespaces have been declared, false otherwise.

Returns:

  • (Boolean)


90
91
92
# File 'lib/kobako/rpc/server.rb', line 90

def empty?
  @namespaces.empty?
end

#encoded_preambleObject

Encode the preamble as msgpack bytes for stdin Frame 1 delivery (SPEC.md B-02). Uses plain MessagePack (no kobako ext types) because the preamble contains only strings — no Handles or Fault envelopes. Structure: [[“Namespace”, [“MemberA”, “MemberB”]], …]. Returns a binary String of msgpack bytes.



108
109
110
# File 'lib/kobako/rpc/server.rb', line 108

def encoded_preamble
  MessagePack.pack(to_preamble)
end

#lookup(target) ⇒ Object

Resolve a target path of the form “Namespace::Member” to the bound Host object. target is a two-level path using the :: separator. Returns the bound Host object. Raises KeyError when the namespace or the member is not bound.

Raises:

  • (KeyError)


64
65
66
67
68
69
70
# File 'lib/kobako/rpc/server.rb', line 64

def lookup(target)
  namespace, member_name, namespace_name = parse_target(target)
  raise KeyError, "no namespace named #{namespace_name.inspect}" if namespace.nil?
  raise KeyError, "no member #{target.inspect} bound on server" unless member_name

  namespace.fetch(member_name)
end

#namespacesObject

Returns all declared Kobako::RPC::Namespace instances as an Array.



80
81
82
# File 'lib/kobako/rpc/server.rb', line 80

def namespaces
  @namespaces.values
end

#reset_handles!Object

Reset the HandleTable for a new #run boundary. Called by Sandbox#run before each invocation (SPEC.md B-19).



126
127
128
# File 'lib/kobako/rpc/server.rb', line 126

def reset_handles!
  @handle_table.reset!
end

#seal!Object

Mark the Server as sealed. Called by Sandbox#run on first run. After sealing, #define raises ArgumentError. Idempotent.



114
115
116
117
# File 'lib/kobako/rpc/server.rb', line 114

def seal!
  @sealed = true
  self
end

#sealed?Boolean

Returns true when #seal! has been called, false otherwise.

Returns:

  • (Boolean)


120
121
122
# File 'lib/kobako/rpc/server.rb', line 120

def sealed?
  @sealed
end

#sizeObject

Returns the number of declared namespaces as an Integer.



85
86
87
# File 'lib/kobako/rpc/server.rb', line 85

def size
  @namespaces.size
end

#to_preambleObject

Structured Frame 1 description. Called by Sandbox#run when assembling stdin Frame 1 (SPEC.md B-02). Returns an unencoded preamble array — an Array of two-element [name, members] arrays, one per declared namespace.



98
99
100
# File 'lib/kobako/rpc/server.rb', line 98

def to_preamble
  @namespaces.values.map(&:to_preamble)
end