From 001dbaeac25705648f4238e505e4d65594b60a10 Mon Sep 17 00:00:00 2001 From: Chris Hines Date: Fri, 17 Oct 2014 21:43:05 -0400 Subject: [PATCH 01/12] Fix #27: Trim files under GOROOT from the top of the stack instead of looking for the function name main.main. --- handler.go | 4 ++-- log15_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ stack/stack.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/handler.go b/handler.go index c5403d1..6289b84 100644 --- a/handler.go +++ b/handler.go @@ -123,7 +123,7 @@ func CallerStackHandler(format string, h Handler) Handler { return FuncHandler(func(r *Record) error { s := stack.Callers(). TrimBelow(stack.Call(r.CallPC[0])). - TrimAboveName("main.main") + TrimRuntime() if len(s) > 0 { buf := &bytes.Buffer{} buf.WriteByte('[') @@ -316,7 +316,7 @@ func LazyHandler(h Handler) Handler { } else { if cs, ok := v.(stack.Trace); ok { v = cs.TrimBelow(stack.Call(r.CallPC[0])). - TrimAboveName("main.main") + TrimRuntime() } r.Ctx[i] = v } diff --git a/log15_test.go b/log15_test.go index 4c3b305..9c96456 100644 --- a/log15_test.go +++ b/log15_test.go @@ -446,3 +446,50 @@ func TestCallerFuncHandler(t *testing.T) { t.Fatalf("Wrong context value, got %s expected string matching %s", s, regex) } } + +// https://github.com/inconshreveable/log15/issues/27 +func TestCallerStackHandler(t *testing.T) { + t.Parallel() + + l := New() + h, r := testHandler() + l.SetHandler(CallerStackHandler("%#v", h)) + + lines := []int{} + + func() { + l.Info("baz") + _, _, line, _ := runtime.Caller(0) + lines = append(lines, line-1) + }() + _, file, line, _ := runtime.Caller(0) + lines = append(lines, line-1) + + if len(r.Ctx) != 2 { + t.Fatalf("Expected stack in record context. Got length %d, expected %d", len(r.Ctx), 2) + } + + const key = "stack" + + if r.Ctx[0] != key { + t.Fatalf("Wrong context key, got %s expected %s", r.Ctx[0], key) + } + + s, ok := r.Ctx[1].(string) + if !ok { + t.Fatalf("Wrong context value type, got %T expected string", r.Ctx[1]) + } + + exp := "[" + for i, line := range lines { + if i > 0 { + exp += " " + } + exp += fmt.Sprint(file, ":", line) + } + exp += "]" + + if s != exp { + t.Fatalf("Wrong context value, got %s expected string matching %s", s, exp) + } +} diff --git a/stack/stack.go b/stack/stack.go index d28eb51..53de594 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -4,6 +4,7 @@ package stack import ( "fmt" + "path/filepath" "runtime" "strings" "sync" @@ -123,6 +124,16 @@ func (pc Call) name() string { return fn.Name() } +func (pc Call) file() string { + pcFix := uintptr(pc) - 1 // work around for go issue #7690 + fn := runtime.FuncForPC(pcFix) + if fn == nil { + return "???" + } + file, _ := fn.FileLine(pcFix) + return file +} + // Trace records a sequence of function invocations from a goroutine stack. type Trace []Call @@ -191,3 +202,29 @@ func (pcs Trace) TrimAboveName(name string) Trace { } return pcs } + +var goroot string + +func init() { + goroot = filepath.ToSlash(runtime.GOROOT()) + if runtime.GOOS == "windows" { + goroot = strings.ToLower(goroot) + } +} + +func inGoroot(path string) bool { + if runtime.GOOS == "windows" { + path = strings.ToLower(path) + } + return strings.HasPrefix(path, goroot) +} + +// TrimRuntime returns a slice of the Trace with the topmost entries from the +// go runtime removed. It considers any calls originating from files under +// GOROOT as part of the runtime. +func (pcs Trace) TrimRuntime() Trace { + for len(pcs) > 0 && inGoroot(pcs[len(pcs)-1].file()) { + pcs = pcs[:len(pcs)-1] + } + return pcs +} From 6bc7db13fba3f870e99b40a4d83d7f2f34edac3b Mon Sep 17 00:00:00 2001 From: Alan Shreve Date: Sun, 26 Oct 2014 12:45:26 -0700 Subject: [PATCH 02/12] add benchmarks for descendant loggers --- bench_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/bench_test.go b/bench_test.go index fc41095..e692e61 100644 --- a/bench_test.go +++ b/bench_test.go @@ -85,3 +85,45 @@ func BenchmarkMultiLevelFilter(b *testing.B) { lg.Info("test message") } } + +func BenchmarkDescendant1(b *testing.B) { + lg := New() + lg.SetHandler(DiscardHandler()) + lg = lg.New() + for i := 0; i < b.N; i++ { + lg.Info("test message") + } +} + +func BenchmarkDescendant2(b *testing.B) { + lg := New() + lg.SetHandler(DiscardHandler()) + for i := 0; i < 2; i++ { + lg = lg.New() + } + for i := 0; i < b.N; i++ { + lg.Info("test message") + } +} + +func BenchmarkDescendant4(b *testing.B) { + lg := New() + lg.SetHandler(DiscardHandler()) + for i := 0; i < 4; i++ { + lg = lg.New() + } + for i := 0; i < b.N; i++ { + lg.Info("test message") + } +} + +func BenchmarkDescendant8(b *testing.B) { + lg := New() + lg.SetHandler(DiscardHandler()) + for i := 0; i < 8; i++ { + lg = lg.New() + } + for i := 0; i < b.N; i++ { + lg.Info("test message") + } +} From 102062feda2f96952014074352544b8e7d05956a Mon Sep 17 00:00:00 2001 From: Alan Shreve Date: Sun, 26 Oct 2014 12:58:56 -0700 Subject: [PATCH 03/12] implement swap handler using atomic pointer operations --- ext/handler.go | 21 ++++++++++----------- handler.go | 14 +++++--------- logger.go | 4 +++- root.go | 3 ++- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/ext/handler.go b/ext/handler.go index 14549b1..e0c5a9e 100644 --- a/ext/handler.go +++ b/ext/handler.go @@ -1,8 +1,11 @@ package ext import ( - log "github.com/inconshreveable/log15" "sync" + "sync/atomic" + "unsafe" + + log "github.com/inconshreveable/log15" ) // EscalateErrHandler wraps another handler and passes all records through @@ -95,23 +98,19 @@ func (h *Speculative) Flush() { // used to implement the SetHandler method for the default // implementation of Logger. func HotSwapHandler(h log.Handler) *HotSwap { - return &HotSwap{handler: h} + hs := new(HotSwap) + hs.Swap(h) + return hs } type HotSwap struct { - mu sync.RWMutex - handler log.Handler + handler unsafe.Pointer } func (h *HotSwap) Log(r *log.Record) error { - defer h.mu.RUnlock() - h.mu.RLock() - err := h.handler.Log(r) - return err + return (*(*log.Handler)(atomic.LoadPointer(&h.handler))).Log(r) } func (h *HotSwap) Swap(newHandler log.Handler) { - h.mu.Lock() - defer h.mu.Unlock() - h.handler = newHandler + atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) } diff --git a/handler.go b/handler.go index c5403d1..190d166 100644 --- a/handler.go +++ b/handler.go @@ -8,6 +8,8 @@ import ( "os" "reflect" "sync" + "sync/atomic" + "unsafe" "github.com/inconshreveable/log15/stack" ) @@ -280,21 +282,15 @@ func BufferedHandler(bufSize int, h Handler) Handler { // swapHandler wraps another handler that may swapped out // dynamically at runtime in a thread-safe fashion. type swapHandler struct { - mu sync.RWMutex - handler Handler + handler unsafe.Pointer } func (h *swapHandler) Log(r *Record) error { - defer h.mu.RUnlock() - h.mu.RLock() - err := h.handler.Log(r) - return err + return (*(*Handler)(atomic.LoadPointer(&h.handler))).Log(r) } func (h *swapHandler) Swap(newHandler Handler) { - h.mu.Lock() - defer h.mu.Unlock() - h.handler = newHandler + atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) } // LazyHandler writes all values to the wrapped handler after evaluating diff --git a/logger.go b/logger.go index 9543a7f..385ed45 100644 --- a/logger.go +++ b/logger.go @@ -112,7 +112,9 @@ func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { } func (l *logger) New(ctx ...interface{}) Logger { - return &logger{append(l.ctx, normalize(ctx)...), &swapHandler{handler: l.h}} + child := &logger{append(l.ctx, normalize(ctx)...), new(swapHandler)} + child.SetHandler(l.h) + return child } func (l *logger) Debug(msg string, ctx ...interface{}) { diff --git a/root.go b/root.go index 666e750..faaece1 100644 --- a/root.go +++ b/root.go @@ -21,7 +21,8 @@ func init() { StderrHandler = StreamHandler(os.Stderr, TerminalFormat()) } - root = &logger{[]interface{}{}, &swapHandler{handler: StdoutHandler}} + root = &logger{[]interface{}{}, new(swapHandler)} + root.SetHandler(StdoutHandler) } // New returns a new logger with the given context. From 315f5fad3a2dfa77efbad32f64261cd15abf635c Mon Sep 17 00:00:00 2001 From: Alan Shreve Date: Mon, 3 Nov 2014 13:52:52 -0800 Subject: [PATCH 04/12] fix potential log record corruption --- log15_test.go | 30 ++++++++++++++++++++++++++++++ logger.go | 12 ++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/log15_test.go b/log15_test.go index 9c96456..7086e02 100644 --- a/log15_test.go +++ b/log15_test.go @@ -9,6 +9,7 @@ import ( "net" "regexp" "runtime" + "sync" "testing" "time" ) @@ -493,3 +494,32 @@ func TestCallerStackHandler(t *testing.T) { t.Fatalf("Wrong context value, got %s expected string matching %s", s, exp) } } + +func TestConcurrent(t *testing.T) { + root := New() + ctxLen := 34 + l := root.New(make([]interface{}, ctxLen)...) + const goroutines = 8 + var res [goroutines]int + l.SetHandler(SyncHandler(FuncHandler(func(r *Record) error { + res[r.Ctx[ctxLen+1].(int)]++ + return nil + }))) + var wg sync.WaitGroup + wg.Add(goroutines) + for i := 0; i < goroutines; i++ { + i := i + go func() { + defer wg.Done() + for j := 0; j < 10000; j++ { + l.Info("test message", "foo", i) + } + }() + } + wg.Wait() + for _, val := range res[:] { + if val != 10000 { + t.Fatalf("Wrong number of messages for context: %+v", res) + } + } +} diff --git a/logger.go b/logger.go index 9543a7f..dac3cf8 100644 --- a/logger.go +++ b/logger.go @@ -100,7 +100,7 @@ func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { Time: time.Now(), Lvl: lvl, Msg: msg, - Ctx: append(l.ctx, normalize(ctx)...), + Ctx: newContext(l.ctx, ctx), KeyNames: RecordKeyNames{ Time: timeKey, Msg: msgKey, @@ -112,7 +112,15 @@ func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { } func (l *logger) New(ctx ...interface{}) Logger { - return &logger{append(l.ctx, normalize(ctx)...), &swapHandler{handler: l.h}} + return &logger{newContext(l.ctx, ctx), &swapHandler{handler: l.h}} +} + +func newContext(prefix []interface{}, suffix []interface{}) []interface{} { + normalizedSuffix := normalize(suffix) + newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) + n := copy(newCtx, prefix) + copy(newCtx[n:], suffix) + return newCtx } func (l *logger) Debug(msg string, ctx ...interface{}) { From 9cb5298aa9dc3db452ca3c3f1f800af208a976a2 Mon Sep 17 00:00:00 2001 From: seong Date: Tue, 4 Nov 2014 12:24:15 +0100 Subject: [PATCH 05/12] add build tag for Go 1.3+. See #31 --- stack/stack.go | 2 ++ stack/stack_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/stack/stack.go b/stack/stack.go index 53de594..490b82b 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -1,3 +1,5 @@ +// +build go1.3 + // Package stack implements utilities to capture, manipulate, and format call // stacks. package stack diff --git a/stack/stack_test.go b/stack/stack_test.go index 52371b1..904f8a5 100644 --- a/stack/stack_test.go +++ b/stack/stack_test.go @@ -1,3 +1,5 @@ +// +build go1.3 + package stack_test import ( From 3e7eeae2e2e70bf7045e1b31eb6e456c662fea8b Mon Sep 17 00:00:00 2001 From: Alan Shreve Date: Tue, 4 Nov 2014 10:28:33 -0800 Subject: [PATCH 06/12] fix typo --- handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler.go b/handler.go index 6dbeb9f..4c771b4 100644 --- a/handler.go +++ b/handler.go @@ -279,7 +279,7 @@ func BufferedHandler(bufSize int, h Handler) Handler { return ChannelHandler(recs) } -// swapHandler wraps another handler that may swapped out +// swapHandler wraps another handler that may be swapped out // dynamically at runtime in a thread-safe fashion. type swapHandler struct { handler unsafe.Pointer From 60f30f8d6a5ab70938a76f6fe71e66904f9417dd Mon Sep 17 00:00:00 2001 From: Alan Shreve Date: Tue, 4 Nov 2014 10:50:03 -0800 Subject: [PATCH 07/12] comment up the test per code review suggestions --- log15_test.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/log15_test.go b/log15_test.go index 7086e02..156d83c 100644 --- a/log15_test.go +++ b/log15_test.go @@ -495,9 +495,22 @@ func TestCallerStackHandler(t *testing.T) { } } +// tests that when logging concurrently to the same logger +// from multiple goroutines that the calls are handled independently +// this test tries to trigger a previous bug where concurrent calls could +// corrupt each other's context values +// +// this test runs N concurrent goroutines each logging a fixed number of +// records and a handler that buckets them based on the index passed in the context. +// if the logger is not concurrent-safe then the values in the buckets will not all be the same +// +// https://github.com/inconshreveable/log15/pull/30 func TestConcurrent(t *testing.T) { root := New() - ctxLen := 34 + // this was the first value that triggered + // go to allocate extra capacity in the logger's context slice which + // was necessary to trigger the bug + const ctxLen = 34 l := root.New(make([]interface{}, ctxLen)...) const goroutines = 8 var res [goroutines]int @@ -508,13 +521,12 @@ func TestConcurrent(t *testing.T) { var wg sync.WaitGroup wg.Add(goroutines) for i := 0; i < goroutines; i++ { - i := i - go func() { + go func(idx int) { defer wg.Done() for j := 0; j < 10000; j++ { - l.Info("test message", "foo", i) + l.Info("test message", "goroutine_idx", idx) } - }() + }(i) } wg.Wait() for _, val := range res[:] { From dc0c4133fc03694dde92731545d6a58c84074365 Mon Sep 17 00:00:00 2001 From: seong Date: Wed, 5 Nov 2014 10:43:22 +0100 Subject: [PATCH 08/12] no go1.3 build tag needed for tests --- stack/stack_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/stack/stack_test.go b/stack/stack_test.go index 904f8a5..52371b1 100644 --- a/stack/stack_test.go +++ b/stack/stack_test.go @@ -1,5 +1,3 @@ -// +build go1.3 - package stack_test import ( From 08181eedcc1fb077a15d6ff0cf75f46c44ff6fa1 Mon Sep 17 00:00:00 2001 From: seong Date: Wed, 5 Nov 2014 11:12:19 +0100 Subject: [PATCH 09/12] go1.2 compatible chan pool implementation --- stack/stack.go | 21 ----------------- stack/stack_pool.go | 25 ++++++++++++++++++++ stack/stack_pool_chan.go | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 stack/stack_pool.go create mode 100644 stack/stack_pool_chan.go diff --git a/stack/stack.go b/stack/stack.go index 490b82b..5b4d096 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -1,5 +1,3 @@ -// +build go1.3 - // Package stack implements utilities to capture, manipulate, and format call // stacks. package stack @@ -9,7 +7,6 @@ import ( "path/filepath" "runtime" "strings" - "sync" ) // Call records a single function invocation from a goroutine stack. It is a @@ -153,24 +150,6 @@ func (pcs Trace) Format(s fmt.State, c rune) { s.Write([]byte("]")) } -var pcStackPool = sync.Pool{ - New: func() interface{} { return make([]uintptr, 1000) }, -} - -// Callers returns a Trace for the current goroutine with element 0 -// identifying the calling function. -func Callers() Trace { - pcs := pcStackPool.Get().([]uintptr) - pcs = pcs[:cap(pcs)] - n := runtime.Callers(2, pcs) - cs := make([]Call, n) - for i, pc := range pcs[:n] { - cs[i] = Call(pc) - } - pcStackPool.Put(pcs) - return cs -} - // TrimBelow returns a slice of the Trace with all entries below pc removed. func (pcs Trace) TrimBelow(pc Call) Trace { for len(pcs) > 0 && pcs[0] != pc { diff --git a/stack/stack_pool.go b/stack/stack_pool.go new file mode 100644 index 0000000..e690a05 --- /dev/null +++ b/stack/stack_pool.go @@ -0,0 +1,25 @@ +// +build go1.3 + +package stack + +import ( + "sync" +) + +var pcStackPool = sync.Pool{ + New: func() interface{} { return make([]uintptr, 1000) }, +} + +// Callers returns a Trace for the current goroutine with element 0 +// identifying the calling function. +func Callers() Trace { + pcs := pcStackPool.Get().([]uintptr) + pcs = pcs[:cap(pcs)] + n := runtime.Callers(2, pcs) + cs := make([]Call, n) + for i, pc := range pcs[:n] { + cs[i] = Call(pc) + } + pcStackPool.Put(pcs) + return cs +} diff --git a/stack/stack_pool_chan.go b/stack/stack_pool_chan.go new file mode 100644 index 0000000..7051fb0 --- /dev/null +++ b/stack/stack_pool_chan.go @@ -0,0 +1,51 @@ +// +build !go1.3 + +package stack + +import ( + "runtime" +) + +const ( + stackPoolSize = 64 +) + +type stackPool struct { + c chan []uintptr +} + +func newStackPool() *stackPool { + return &stackPool{c: make(chan []uintptr, stackPoolSize)} +} + +func (p *stackPool) Get() []uintptr { + select { + case st := <-p.c: + return st + default: + return make([]uintptr, 1000) + } +} + +func (p *stackPool) Put(st []uintptr) { + select { + case p.c <- st: + default: + } +} + +var pcStackPool = newStackPool() + +// Callers returns a Trace for the current goroutine with element 0 +// identifying the calling function. +func Callers() Trace { + pcs := pcStackPool.Get() + pcs = pcs[:cap(pcs)] + n := runtime.Callers(2, pcs) + cs := make([]Call, n) + for i, pc := range pcs[:n] { + cs[i] = Call(pc) + } + pcStackPool.Put(pcs) + return cs +} From 7ee94cd171be7bfb2c35909eb660d289d0ffa7e5 Mon Sep 17 00:00:00 2001 From: seong Date: Wed, 5 Nov 2014 11:18:20 +0100 Subject: [PATCH 10/12] add travis config --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a66c49b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - 1.3 + - release + - tip From 4ce6c3853cc710afd192ed4932e691e9ba7fe5d8 Mon Sep 17 00:00:00 2001 From: Alan Shreve Date: Wed, 5 Nov 2014 12:05:53 -0800 Subject: [PATCH 11/12] proper colors on windows thanks to mattn. fixes #13 --- root.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/root.go b/root.go index faaece1..c5118d4 100644 --- a/root.go +++ b/root.go @@ -4,6 +4,7 @@ import ( "os" "github.com/inconshreveable/log15/term" + "github.com/mattn/go-colorable" ) var ( @@ -14,11 +15,11 @@ var ( func init() { if term.IsTty(os.Stdout.Fd()) { - StdoutHandler = StreamHandler(os.Stdout, TerminalFormat()) + StdoutHandler = StreamHandler(colorable.NewColorableStdout(), TerminalFormat()) } if term.IsTty(os.Stderr.Fd()) { - StderrHandler = StreamHandler(os.Stderr, TerminalFormat()) + StderrHandler = StreamHandler(colorable.NewColorableStderr(), TerminalFormat()) } root = &logger{[]interface{}{}, new(swapHandler)} From a619218dd05e60e5cedf0759187ae3cc3f0a23bc Mon Sep 17 00:00:00 2001 From: seong Date: Thu, 6 Nov 2014 10:48:50 +0100 Subject: [PATCH 12/12] Callers now go version independent, add poolBuf() and putPoolBuf() funcs --- stack/stack.go | 14 ++++++++++++++ stack/stack_pool.go | 18 ++++++------------ stack/stack_pool_chan.go | 40 ++++++++-------------------------------- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/stack/stack.go b/stack/stack.go index 5b4d096..ae3021c 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -112,6 +112,20 @@ func (pc Call) Format(s fmt.State, c rune) { } } +// Callers returns a Trace for the current goroutine with element 0 +// identifying the calling function. +func Callers() Trace { + pcs := poolBuf() + pcs = pcs[:cap(pcs)] + n := runtime.Callers(2, pcs) + cs := make([]Call, n) + for i, pc := range pcs[:n] { + cs[i] = Call(pc) + } + putPoolBuf(pcs) + return cs +} + // name returns the import path qualified name of the function containing the // call. func (pc Call) name() string { diff --git a/stack/stack_pool.go b/stack/stack_pool.go index e690a05..34f2ca9 100644 --- a/stack/stack_pool.go +++ b/stack/stack_pool.go @@ -10,16 +10,10 @@ var pcStackPool = sync.Pool{ New: func() interface{} { return make([]uintptr, 1000) }, } -// Callers returns a Trace for the current goroutine with element 0 -// identifying the calling function. -func Callers() Trace { - pcs := pcStackPool.Get().([]uintptr) - pcs = pcs[:cap(pcs)] - n := runtime.Callers(2, pcs) - cs := make([]Call, n) - for i, pc := range pcs[:n] { - cs[i] = Call(pc) - } - pcStackPool.Put(pcs) - return cs +func poolBuf() []uintptr { + return pcStackPool.Get().([]uintptr) +} + +func putPoolBuf(p []uintptr) { + pcStackPool.Put(p) } diff --git a/stack/stack_pool_chan.go b/stack/stack_pool_chan.go index 7051fb0..aa449ed 100644 --- a/stack/stack_pool_chan.go +++ b/stack/stack_pool_chan.go @@ -2,50 +2,26 @@ package stack -import ( - "runtime" -) - const ( stackPoolSize = 64 ) -type stackPool struct { - c chan []uintptr -} - -func newStackPool() *stackPool { - return &stackPool{c: make(chan []uintptr, stackPoolSize)} -} +var ( + pcStackPool = make(chan []uintptr, stackPoolSize) +) -func (p *stackPool) Get() []uintptr { +func poolBuf() []uintptr { select { - case st := <-p.c: - return st + case p := <-pcStackPool: + return p default: return make([]uintptr, 1000) } } -func (p *stackPool) Put(st []uintptr) { +func putPoolBuf(p []uintptr) { select { - case p.c <- st: + case pcStackPool <- p: default: } } - -var pcStackPool = newStackPool() - -// Callers returns a Trace for the current goroutine with element 0 -// identifying the calling function. -func Callers() Trace { - pcs := pcStackPool.Get() - pcs = pcs[:cap(pcs)] - n := runtime.Callers(2, pcs) - cs := make([]Call, n) - for i, pc := range pcs[:n] { - cs[i] = Call(pc) - } - pcStackPool.Put(pcs) - return cs -}