From 45034814d8412c610674bc1be3042f3e14a1c0cb Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 12 Sep 2013 17:51:09 +0100 Subject: [PATCH] Add pkg/leak, to help find leaks. Change-Id: I52b15f168f63c5ac40c40f258d1f3ff2ffd4965e --- pkg/leak/leak.go | 63 +++++++++++++++++++++++++++++++++++++++++++ pkg/leak/leak_test.go | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 pkg/leak/leak.go create mode 100644 pkg/leak/leak_test.go diff --git a/pkg/leak/leak.go b/pkg/leak/leak.go new file mode 100644 index 000000000..113229af8 --- /dev/null +++ b/pkg/leak/leak.go @@ -0,0 +1,63 @@ +/* +Copyright 2013 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package leak + +import ( + "bytes" + "fmt" + "log" + "runtime" +) + +// A Checker checks for leaks. +type Checker struct { + pc []uintptr // nil once closed +} + +// NewChecker returns a Checker, remembering the stack trace. +func NewChecker() *Checker { + pc := make([]uintptr, 50) + ch := &Checker{pc[:runtime.Callers(0, pc)]} + runtime.SetFinalizer(ch, (*Checker).finalize) + return ch +} + +func (c *Checker) Close() { + if c != nil { + c.pc = nil + } +} + +func (c *Checker) finalize() { + if c == nil || c.pc == nil { + return + } + nTestLeaks++ // for testing + var buf bytes.Buffer + buf.WriteString("Leak at:\n") + for _, pc := range c.pc { + f := runtime.FuncForPC(pc) + if f == nil { + break + } + file, line := f.FileLine(f.Entry()) + fmt.Fprintf(&buf, " %s:%d\n", file, line) + } + log.Println(buf.String()) +} + +var nTestLeaks int diff --git a/pkg/leak/leak_test.go b/pkg/leak/leak_test.go new file mode 100644 index 000000000..0cd792511 --- /dev/null +++ b/pkg/leak/leak_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2013 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package leak + +import ( + "runtime" + "testing" +) + +func TestLeak(t *testing.T) { + testLeak(t, false, 1) +} + +func TestNoLeak(t *testing.T) { + testLeak(t, true, 0) +} + +func testLeak(t *testing.T, close bool, want int) { + c := make(chan bool) + go func() { + ch := NewChecker() + if close { + ch.Close() + } + c <- true + }() + <-c + leak0 := nTestLeaks + runtime.GC() + leaks := nTestLeaks - leak0 + if leaks != want { + t.Errorf("got %d leaks; want %d", want) + } +}