Skip to content

Commit

Permalink
Move all cap table methods from Message to CapTable.
Browse files Browse the repository at this point in the history
  • Loading branch information
lthibault committed Apr 2, 2023
1 parent 1cba92d commit 4b493fa
Show file tree
Hide file tree
Showing 24 changed files with 195 additions and 153 deletions.
2 changes: 1 addition & 1 deletion answer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestPromiseFulfill(t *testing.T) {
defer msg.Release()

res, _ := NewStruct(seg, ObjectSize{PointerCount: 3})
res.SetPtr(1, NewInterface(seg, msg.AddCap(c.AddRef())).ToPtr())
res.SetPtr(1, NewInterface(seg, msg.CapTable().Add(c.AddRef())).ToPtr())

p.Fulfill(res.ToPtr())

Expand Down
16 changes: 6 additions & 10 deletions capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,12 @@ func (i Interface) value(paddr address) rawPointer {

// Client returns the client stored in the message's capability table
// or nil if the pointer is invalid.
func (i Interface) Client() Client {
msg := i.Message()
if msg == nil {
return Client{}
}
tab := msg.capTable
if int64(i.cap) >= int64(len(tab)) {
return Client{}
func (i Interface) Client() (c Client) {
if msg := i.Message(); msg != nil {
c = msg.CapTable().Get(i)
}
return tab[i.cap]

return
}

// A CapabilityID is an index into a message's capability table.
Expand Down Expand Up @@ -668,7 +664,7 @@ func (c Client) Release() {
}

func (c Client) EncodeAsPtr(seg *Segment) Ptr {
capId := seg.Message().AddCap(c)
capId := seg.Message().CapTable().Add(c)
return NewInterface(seg, capId).ToPtr()
}

Expand Down
2 changes: 1 addition & 1 deletion capnpc-go/templates/structCapabilityField
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ func (s {{.Node.Name}}) Set{{.Field.Name|title}}(c {{.FieldType}}) error {
return capnp.Struct(s).SetPtr({{.Field.Slot.Offset}}, capnp.Ptr{})
}
seg := s.Segment()
in := capnp.NewInterface(seg, seg.Message().AddCap(c))
in := capnp.NewInterface(seg, seg.Message().CapTable().Add(c))
return capnp.Struct(s).SetPtr({{.Field.Slot.Offset}}, in.ToPtr())
}
2 changes: 1 addition & 1 deletion capnpc-go/templates/structInterfaceField
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func (s {{.Node.Name}}) Set{{.Field.Name|title}}(v {{.FieldType}}) error {
return capnp.Struct(s).SetPtr({{.Field.Slot.Offset}}, capnp.Ptr{})
}
seg := s.Segment()
in := capnp.NewInterface(seg, seg.Message().AddCap(capnp.Client(v)))
in := capnp.NewInterface(seg, seg.Message().CapTable().Add(capnp.Client(v)))
return capnp.Struct(s).SetPtr({{.Field.Slot.Offset}}, in.ToPtr())
}

45 changes: 45 additions & 0 deletions captable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package capnp

type CapTable struct {
cs []Client
}

func (ct *CapTable) Reset(cs ...Client) {
for _, c := range ct.cs {
c.Release()
}

ct.cs = append(ct.cs[:0], cs...)
}

func (ct CapTable) Len() int {
return len(ct.cs)
}

func (ct CapTable) At(i int) Client {
return ct.cs[i]
}

func (ct CapTable) Contains(ifc Interface) bool {
return ifc.IsValid() && ifc.Capability() < CapabilityID(ct.Len())
}

func (ct CapTable) Get(ifc Interface) (c Client) {
if ct.Contains(ifc) {
c = ct.cs[ifc.Capability()]
}

return
}

func (ct CapTable) Set(id CapabilityID, c Client) {
ct.cs[id] = c
}

// Add appends a capability to the message's capability table and
// returns its ID. It "steals" c's reference: the Message will release
// the client when calling Reset.
func (ct *CapTable) Add(c Client) CapabilityID {
ct.cs = append(ct.cs, c)
return CapabilityID(ct.Len() - 1)
}
34 changes: 34 additions & 0 deletions captable_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package capnp_test

import (
"errors"
"testing"

"capnproto.org/go/capnp/v3"
"github.com/stretchr/testify/assert"
)

func TestCapTable(t *testing.T) {
t.Parallel()

var ct capnp.CapTable

assert.Zero(t, ct.Len(),
"zero-value CapTable should be empty")
assert.Zero(t, ct.Add(capnp.Client{}),
"first entry should have CapabilityID(0)")
assert.Equal(t, 1, ct.Len(),
"should increase length after adding capability")

ct.Reset()
assert.Zero(t, ct.Len(),
"zero-value CapTable should be empty after Reset()")
ct.Reset(capnp.Client{}, capnp.Client{})
assert.Equal(t, 2, ct.Len(),
"zero-value CapTable should be empty after Reset(c, c)")

errTest := errors.New("test")
ct.Set(capnp.CapabilityID(0), capnp.ErrorClient(errTest))
err := ct.At(0).State().Brand.Value.(error)
assert.ErrorIs(t, errTest, err, "should update client at index 0")
}
8 changes: 4 additions & 4 deletions internal/aircraftlib/aircraft.capnp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion list.go
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ func (c CapList[T]) At(i int) (T, error) {
func (c CapList[T]) Set(i int, v T) error {
pl := PointerList(c)
seg := pl.Segment()
capId := seg.Message().AddCap(Client(v))
capId := seg.Message().CapTable().Add(Client(v))
return pl.Set(i, NewInterface(seg, capId).ToPtr())
}

Expand Down
2 changes: 1 addition & 1 deletion localpromise.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (lp localPromise) String() string {

func (lp localPromise) Fulfill(c Client) {
msg, seg := NewSingleSegmentMessage(nil)
capID := msg.AddCap(c)
capID := msg.CapTable().Add(c)
lp.aq.Fulfill(NewInterface(seg, capID).ToPtr())
}

Expand Down
42 changes: 4 additions & 38 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,6 @@ const (

const maxDepth = ^uint(0)

type CapTable []Client

func (ct CapTable) Len() int {
return len(ct)
}

func (ct CapTable) Contains(ifc Interface) bool {
return ifc.IsValid() && ifc.Capability() < CapabilityID(ct.Len())
}

func (ct CapTable) Get(ifc Interface) (c Client) {
if ct.Contains(ifc) {
c = ct[ifc.Capability()]
}

return
}

// A Message is a tree of Cap'n Proto objects, split into one or more
// segments of contiguous memory. The only required field is Arena.
// A Message is safe to read from multiple goroutines.
Expand Down Expand Up @@ -123,10 +105,7 @@ func (m *Message) Release() {
// the Message, releases all clients in the cap table, and
// releases the current Arena, so use with caution.
func (m *Message) Reset(arena Arena) (first *Segment, err error) {
for _, c := range m.capTable {
c.Release()
}

m.capTable.Reset()
for k := range m.segs {
delete(m.segs, k)
}
Expand All @@ -139,7 +118,7 @@ func (m *Message) Reset(arena Arena) (first *Segment, err error) {
Arena: arena,
TraverseLimit: m.TraverseLimit,
DepthLimit: m.DepthLimit,
capTable: m.capTable[:0],
capTable: m.capTable,
segs: m.segs,
}

Expand Down Expand Up @@ -240,21 +219,8 @@ func (m *Message) SetRoot(p Ptr) error {
return nil
}

func (m *Message) CapTable() CapTable {
return m.capTable
}

func (m *Message) SetCapTable(ct []Client) {
m.capTable = ct
}

// AddCap appends a capability to the message's capability table and
// returns its ID. It "steals" c's reference: the Message will release
// the client when calling Reset.
func (m *Message) AddCap(c Client) CapabilityID {
n := CapabilityID(len(m.capTable))
m.capTable = append(m.capTable, c)
return n
func (m *Message) CapTable() *CapTable {
return &m.capTable
}

// Compute the total size of the message in bytes, when serialized as
Expand Down
60 changes: 35 additions & 25 deletions message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,39 +400,49 @@ func TestAddCap(t *testing.T) {
msg := &Message{Arena: SingleSegment(nil)}

// Simple case: distinct non-nil clients.
id1 := msg.AddCap(client1.AddRef())
assert.Equal(t, CapabilityID(0), id1, "first capability ID should be 0")
assert.Len(t, msg.capTable, 1, "should have exactly one capability in the capTable")
assert.True(t, msg.capTable[0].IsSame(client1), "client does not match entry in cap table")

id2 := msg.AddCap(client2.AddRef())
assert.Equal(t, CapabilityID(1), id2, "second capability ID should be 1")
assert.Len(t, msg.capTable, 2, "should have exactly two capabilities in the capTable")
assert.True(t, msg.capTable[1].IsSame(client2), "client does not match entry in cap table")
id1 := msg.CapTable().Add(client1.AddRef())
assert.Equal(t, CapabilityID(0), id1,
"first capability ID should be 0")
assert.Equal(t, 1, msg.CapTable().Len(),
"should have exactly one capability in the capTable")
assert.True(t, msg.CapTable().At(0).IsSame(client1),
"client does not match entry in cap table")

id2 := msg.CapTable().Add(client2.AddRef())
assert.Equal(t, CapabilityID(1), id2,
"second capability ID should be 1")
assert.Equal(t, 2, msg.CapTable().Len(),
"should have exactly two capabilities in the capTable")
assert.True(t, msg.CapTable().At(1).IsSame(client2),
"client does not match entry in cap table")

// nil client
id3 := msg.AddCap(Client{})
assert.Equal(t, CapabilityID(2), id3, "third capability ID should be 2")
assert.Len(t, msg.capTable, 3, "should have exactly three capabilities in the capTable")
assert.True(t, msg.capTable[2].IsSame(Client{}), "client does not match entry in cap table")

// AddCap should not attempt to deduplicate.
id4 := msg.AddCap(client1.AddRef())
assert.Equal(t, CapabilityID(3), id4, "fourth capability ID should be 3")
assert.Len(t, msg.capTable, 4, "should have exactly four capabilities in the capTable")
assert.True(t, msg.capTable[3].IsSame(client1), "client does not match entry in cap table")

// Verify that AddCap steals the reference: once client1 and client2
id3 := msg.CapTable().Add(Client{})
assert.Equal(t, CapabilityID(2), id3,
"third capability ID should be 2")
assert.Equal(t, 3, msg.CapTable().Len(),
"should have exactly three capabilities in the capTable")
assert.True(t, msg.CapTable().At(2).IsSame(Client{}),
"client does not match entry in cap table")

// Add should not attempt to deduplicate.
id4 := msg.CapTable().Add(client1.AddRef())
assert.Equal(t, CapabilityID(3), id4,
"fourth capability ID should be 3")
assert.Equal(t, 4, msg.CapTable().Len(),
"should have exactly four capabilities in the capTable")
assert.True(t, msg.CapTable().At(3).IsSame(client1),
"client does not match entry in cap table")

// Verify that Add steals the reference: once client1 and client2
// and the message's capabilities released, hook1 and hook2 should be
// shut down. If they are not, then AddCap created a new reference.
// shut down. If they are not, then Add created a new reference.
client1.Release()
assert.Zero(t, hook1.shutdowns, "hook1 shut down before releasing msg.capTable")
client2.Release()
assert.Zero(t, hook2.shutdowns, "hook2 shut down before releasing msg.capTable")

for _, c := range msg.capTable {
c.Release()
}
msg.CapTable().Reset()

assert.NotZero(t, hook1.shutdowns, "hook1 not shut down after releasing msg.capTable")
assert.NotZero(t, hook2.shutdowns, "hook2 not shut down after releasing msg.capTable")
Expand Down
4 changes: 2 additions & 2 deletions pogs/insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func (ins *inserter) insertField(s capnp.Struct, f schema.Field, val reflect.Val
if !c.IsValid() {
return s.SetPtr(off, capnp.Ptr{})
}
id := s.Message().AddCap(c)
id := s.Message().CapTable().Add(c)
return s.SetPtr(off, capnp.NewInterface(s.Segment(), id).ToPtr())
default:
panic("unreachable")
Expand All @@ -255,7 +255,7 @@ func capPtr(seg *capnp.Segment, val reflect.Value) capnp.Ptr {
if !client.IsValid() {
return capnp.Ptr{}
}
cap := seg.Message().AddCap(client)
cap := seg.Message().CapTable().Add(client)
iface := capnp.NewInterface(seg, cap)
return iface.ToPtr()
}
Expand Down
2 changes: 1 addition & 1 deletion pogs/pogs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func newTestList() capnp.List {

func newTestInterface() capnp.Interface {
msg, seg, _ := capnp.NewMessage(capnp.SingleSegment(nil))
id := msg.AddCap(capnp.ErrorClient(errors.New("boo")))
id := msg.CapTable().Add(capnp.ErrorClient(errors.New("boo")))
return capnp.NewInterface(seg, id)
}

Expand Down
4 changes: 2 additions & 2 deletions pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ func Equal(p1, p2 Ptr) (bool, error) {
if i1.Capability() == i2.Capability() {
return true, nil
}
ntab := len(i1.Message().capTable)
if int64(i1.Capability()) >= int64(ntab) || int64(i2.Capability()) >= int64(ntab) {

if !i1.Message().CapTable().Contains(i1) || !i1.Message().CapTable().Contains(i2) {
return false, nil
}
}
Expand Down
14 changes: 7 additions & 7 deletions pointer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ func TestEqual(t *testing.T) {
plistB, _ := NewPointerList(seg, 1)
plistB.Set(0, structB.ToPtr())
ec := ErrorClient(errors.New("boo"))
msg.capTable = []Client{
0: ec,
1: ec,
2: ErrorClient(errors.New("another boo")),
3: {},
4: {},
}
msg.CapTable().Reset(
ec,
ec,
ErrorClient(errors.New("another boo")),
Client{},
Client{},
)
iface1 := NewInterface(seg, 0)
iface2 := NewInterface(seg, 1)
ifaceAlt := NewInterface(seg, 2)
Expand Down
Loading

0 comments on commit 4b493fa

Please sign in to comment.