From 83768a6890f64bb78f2983eabf5f3d4c01641d70 Mon Sep 17 00:00:00 2001 From: Ian Denhardt Date: Fri, 9 Dec 2022 20:59:34 -0500 Subject: [PATCH] Report full stack traces when detecting leaked clients. This patch adjusts the logic for detecting client leaks to include full stack traces for where they were created, rather than just the source location. --- capability.go | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/capability.go b/capability.go index 2e90c598..2f98d66a 100644 --- a/capability.go +++ b/capability.go @@ -7,6 +7,7 @@ import ( "strconv" "sync" + "capnproto.org/go/capnp/v3/exp/bufferpool" "capnproto.org/go/capnp/v3/flowcontrol" "capnproto.org/go/capnp/v3/internal/syncutil" ) @@ -114,9 +115,10 @@ type ClientKind = struct { } type client struct { - creatorFunc int - creatorFile string - creatorLine int + creatorFunc int + creatorFile string + creatorStack string + creatorLine int mu sync.Mutex // protects the struct limiter flowcontrol.FlowLimiter @@ -147,6 +149,19 @@ type clientHook struct { resolvedHook *clientHook // valid only if resolved is closed } +func (c Client) setupLeakReporting(creatorFunc int) { + if clientLeakFunc == nil { + return + } + c.creatorFunc = creatorFunc + _, c.creatorFile, c.creatorLine, _ = runtime.Caller(2) + buf := bufferpool.Default.Get(1e6) + n := runtime.Stack(buf, false) + c.creatorStack = string(buf[:n]) + bufferpool.Default.Put(buf) + c.setFinalizer() +} + // NewClient creates the first reference to a capability. // If hook is nil, then NewClient returns nil. // @@ -165,11 +180,7 @@ func NewClient(hook ClientHook) Client { } h.resolvedHook = h c := Client{client: &client{h: h}} - if clientLeakFunc != nil { - c.creatorFunc = 1 - _, c.creatorFile, c.creatorLine, _ = runtime.Caller(1) - c.setFinalizer() - } + c.setupLeakReporting(1) return c } @@ -192,11 +203,7 @@ func NewPromisedClient(hook ClientHook) (Client, *ClientPromise) { metadata: *NewMetadata(), } c := Client{client: &client{h: h}} - if clientLeakFunc != nil { - c.creatorFunc = 2 - _, c.creatorFile, c.creatorLine, _ = runtime.Caller(1) - c.setFinalizer() - } + c.setupLeakReporting(2) return c, &ClientPromise{h: h} } @@ -460,11 +467,7 @@ func (c Client) AddRef() Client { c.h.refs++ c.h.mu.Unlock() d := Client{client: &client{h: c.h}} - if clientLeakFunc != nil { - d.creatorFunc = 3 - _, d.creatorFile, d.creatorLine, _ = runtime.Caller(1) - d.setFinalizer() - } + d.setupLeakReporting(3) return d } @@ -653,6 +656,9 @@ func finalizeClient(c *client) { } else { msg = fmt.Sprintf("leaked client created by %s on %s:%d", fname, c.creatorFile, c.creatorLine) } + if c.creatorStack != "" { + msg += "\nCreation stack trace:\n" + c.creatorStack + "\n" + } // finalizeClient will only be called if clientLeakFunc != nil. go clientLeakFunc(msg)