diff --git a/answer.go b/answer.go index 76baa2ed..6623368d 100644 --- a/answer.go +++ b/answer.go @@ -540,6 +540,13 @@ func (pc PipelineClient) Brand() Brand { func (pc PipelineClient) Shutdown() { } +func (pc PipelineClient) String() string { + return "PipelineClient{transform: " + + str.Slice(pc.transform) + + ", promise: 0x" + str.PtrToHex(pc.p) + + "}" +} + // A PipelineOp describes a step in transforming a pipeline. // It maps closely with the PromisedAnswer.Op struct in rpc.capnp. type PipelineOp struct { diff --git a/capability.go b/capability.go index 1708ce4c..226973ff 100644 --- a/capability.go +++ b/capability.go @@ -3,7 +3,6 @@ package capnp import ( "context" "errors" - "fmt" "runtime" "strconv" "sync" @@ -606,9 +605,9 @@ func (c Client) String() string { } var s string if c.h.isResolved() { - s = fmt.Sprintf("", c.h.ClientHook, c.h) + s = "" } else { - s = fmt.Sprintf("", c.h.ClientHook, c.h) + s = "" } c.h.mu.Unlock() c.mu.Unlock() @@ -876,6 +875,9 @@ type ClientHook interface { // Shutdown is undefined. It is expected for the ClientHook to reject // any outstanding call futures. Shutdown() + + // String formats the hook as a string (same as fmt.Stringer) + String() string } // Send is the input to ClientHook.Send. @@ -1049,4 +1051,8 @@ func (ec errorClient) Brand() Brand { func (ec errorClient) Shutdown() { } +func (ec errorClient) String() string { + return "errorClient{" + ec.e.Error() + "}" +} + var closedSignal = make(chan struct{}) diff --git a/capability_test.go b/capability_test.go index dce001d4..f701e3cd 100644 --- a/capability_test.go +++ b/capability_test.go @@ -268,6 +268,10 @@ type dummyHook struct { shutdowns int } +func (dh *dummyHook) String() string { + return "&dummyHook{}" +} + func (dh *dummyHook) Send(_ context.Context, s Send) (*Answer, ReleaseFunc) { dh.calls++ return ImmediateAnswer(s.Method, newEmptyStruct()), func() {} diff --git a/flowcontrol/fixed.go b/flowcontrol/fixed.go index e61b20bf..4bb92265 100644 --- a/flowcontrol/fixed.go +++ b/flowcontrol/fixed.go @@ -2,8 +2,8 @@ package flowcontrol import ( "context" - "fmt" + "capnproto.org/go/capnp/v3/internal/str" "golang.org/x/sync/semaphore" ) @@ -26,7 +26,9 @@ func (fl *fixedLimiter) StartMessage(ctx context.Context, size uint64) (gotRespo // reservation on the semaphore. We can't return an error because it // is currently ignored by the caller. if int64(size) > fl.size { - panic(fmt.Sprintf("StartMessage(): message size %d is too large (max %d)", size, fl.size)) + panic("StartMessage(): message size " + + str.Utod(size) + + " is too large (max " + str.Itod(fl.size) + ")") } if err = fl.sem.Acquire(ctx, int64(size)); err == nil { diff --git a/internal/str/str.go b/internal/str/str.go index 84b4c21f..340250bb 100644 --- a/internal/str/str.go +++ b/internal/str/str.go @@ -2,7 +2,12 @@ // of environments that care about minimizing executable size. package str -import "strconv" +import ( + "strconv" + "strings" + + "unsafe" // Only for formatting pointers as integers; we don't actually do anything unsafe. +) // Utod formats unsigned integers as decimals. func Utod[T Uint](n T) string { @@ -19,6 +24,10 @@ func UToHex[T Uint](n T) string { return strconv.FormatUint(uint64(n), 16) } +func PtrToHex[T any](p *T) string { + return UToHex(uintptr(unsafe.Pointer(p))) +} + // ZeroPad pads value to the left with zeros, making the resulting string // count bytes long. func ZeroPad(count int, value string) string { @@ -34,8 +43,27 @@ func ZeroPad(count int, value string) string { return string(buf) } +// Slice formats a slice of values which themselves implement Stringer. +func Slice[T Stringer](s []T) string { + var b strings.Builder + b.WriteRune('{') + for i, v := range s { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(v.String()) + } + b.WriteRune('}') + return b.String() +} + +// Stringer is equivalent to fmt.Stringer +type Stringer interface { + String() string +} + type Uint interface { - ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint + ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint | ~uintptr } type Int interface { diff --git a/rpc/export.go b/rpc/export.go index 1f1ca657..8cfd9917 100644 --- a/rpc/export.go +++ b/rpc/export.go @@ -208,6 +208,10 @@ type embargo struct { lifted chan struct{} } +func (e embargo) String() string { + return "embargo{c: " + e.c.String() + ", 0x" + str.PtrToHex(e.p) + "}" +} + // embargo creates a new embargoed client, stealing the reference. // // The caller must be holding onto c.mu. diff --git a/rpc/import.go b/rpc/import.go index 736183fc..b99b4393 100644 --- a/rpc/import.go +++ b/rpc/import.go @@ -5,6 +5,7 @@ import ( "errors" "capnproto.org/go/capnp/v3" + "capnproto.org/go/capnp/v3/internal/str" "capnproto.org/go/capnp/v3/internal/syncutil" rpccp "capnproto.org/go/capnp/v3/std/capnp/rpc" ) @@ -84,6 +85,10 @@ type importClient struct { generation uint64 } +func (ic *importClient) String() string { + return "importClient{c: 0x" + str.PtrToHex(ic.c) + ", id: " + str.Utod(ic.id) + "}" +} + func (ic *importClient) Send(ctx context.Context, s capnp.Send) (*capnp.Answer, capnp.ReleaseFunc) { return withLockedConn2(ic.c, func(c *lockedConn) (*capnp.Answer, capnp.ReleaseFunc) { if !c.startTask() { diff --git a/rpc/rpc.go b/rpc/rpc.go index 1e4a330b..7928eeb4 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -332,6 +332,10 @@ type bootstrapClient struct { cancel context.CancelFunc } +func (bc bootstrapClient) String() string { + return "bootstrapClient{c: " + bc.c.String() + "}" +} + func (bc bootstrapClient) Send(ctx context.Context, s capnp.Send) (*capnp.Answer, capnp.ReleaseFunc) { return bc.c.SendCall(ctx, s) } diff --git a/server/server.go b/server/server.go index 1434ee2c..42e8cdab 100644 --- a/server/server.go +++ b/server/server.go @@ -10,6 +10,7 @@ import ( "capnproto.org/go/capnp/v3" "capnproto.org/go/capnp/v3/exc" "capnproto.org/go/capnp/v3/exp/mpsc" + "capnproto.org/go/capnp/v3/internal/str" ) // A Method describes a single capability method on a server object. @@ -104,6 +105,10 @@ type Server struct { HandleUnknownMethod func(m capnp.Method) *Method } +func (s *Server) String() string { + return "*Server@0x" + str.PtrToHex(s) +} + // New returns a client hook that makes calls to a set of methods. // If shutdown is nil then the server's shutdown is a no-op. The server // guarantees message delivery order by blocking each call on the