Class: Kobako::Registry::HandleTable

Inherits:
Object
  • Object
show all
Defined in:
lib/kobako/registry/handle_table.rb

Overview

Host-side mapping from opaque integer Handle IDs to Ruby objects (capability proxies). One table is owned per Kobako::Registry instance (and therefore per Kobako::Sandbox instance). See SPEC.md B-15.

Lifecycle invariants (SPEC.md):

- {SPEC.md B-15}[link:../../../SPEC.md] — Handle IDs are allocated by
  a monotonically increasing counter scoped to a single `#run`. The
  first ID issued in a run is 1; ID 0 is reserved as the invalid
  sentinel and is never returned by #alloc.

- {SPEC.md B-19}[link:../../../SPEC.md] — When between `#run`
  invocations (via `#reset!`), every Handle issued under the old state
  becomes invalid.

- {SPEC.md B-21}[link:../../../SPEC.md] — The cap is `0x7fff_ffff`
  (2³¹ − 1). Allocation beyond the cap raises immediately — no silent
  truncation, no wrap, no ID reuse.

Instance Method Summary collapse

Constructor Details

#initialize(next_id: 1) ⇒ HandleTable

Build a fresh, empty HandleTable. next_id is an internal seam that sets the starting value of the monotonic counter (defaults to 1 per B-15); tests pass a value near Wire::Handle::MAX_ID to exercise the cap-exhaustion path without 2³¹ allocations.



31
32
33
34
# File 'lib/kobako/registry/handle_table.rb', line 31

def initialize(next_id: 1)
  @entries = {}
  @next_id = next_id
end

Instance Method Details

#alloc(object) ⇒ Object

Bind object in the table and return its newly-allocated Handle ID. object is any host-side Ruby object to bind. Returns a freshly- allocated Handle ID in [1, Wire::Handle::MAX_ID]. Raises Kobako::HandleTableExhausted if the next ID would exceed the cap. The cap is anchored on Wire::Handle — the wire codec and the allocator share the same invariant (SPEC.md B-21).



42
43
44
45
46
47
48
49
50
# File 'lib/kobako/registry/handle_table.rb', line 42

def alloc(object)
  id = @next_id
  cap = Wire::Handle::MAX_ID
  raise HandleTableExhausted, "HandleTable exhausted: id #{id} exceeds MAX_ID #{cap}" if id > cap

  @entries[id] = object
  @next_id = id + 1
  id
end

#fetch(id) ⇒ Object

Resolve a Handle ID to its bound object. id is a Handle ID previously returned by #alloc. Returns the bound object. Raises Kobako::HandleTableError if id is not currently bound.



55
56
57
58
# File 'lib/kobako/registry/handle_table.rb', line 55

def fetch(id)
  require_bound!(id)
  @entries[id]
end

#include?(id) ⇒ Boolean

Returns true when id is currently bound, false otherwise.

Returns:

  • (Boolean)


91
92
93
# File 'lib/kobako/registry/handle_table.rb', line 91

def include?(id)
  @entries.key?(id)
end

#mark_disconnected(id) ⇒ Object

Mark the entry at id as disconnected (ABA protection). id is the Handle ID to poison; silently ignored if id is not currently bound. Returns self for chainability, matching the convention of #reset!.



80
81
82
83
# File 'lib/kobako/registry/handle_table.rb', line 80

def mark_disconnected(id)
  @entries[id] = :disconnected if @entries.key?(id)
  self
end

#release(id) ⇒ Object

Remove and return the binding for id. id is the Handle ID to release. Returns the previously-bound object. Raises Kobako::HandleTableError if id is not currently bound.



63
64
65
66
# File 'lib/kobako/registry/handle_table.rb', line 63

def release(id)
  require_bound!(id)
  @entries.delete(id)
end

#reset!Object

Clear all entries AND reset the counter to 1. Called at the per-run boundary — see SPEC.md B-19. Returns self.



71
72
73
74
75
# File 'lib/kobako/registry/handle_table.rb', line 71

def reset!
  @entries.clear
  @next_id = 1
  self
end

#sizeObject

Returns the number of currently-bound entries.



86
87
88
# File 'lib/kobako/registry/handle_table.rb', line 86

def size
  @entries.size
end