This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 7a7b414d1f GH-35212: [Go] Add ability to show full call stack with 
ARROW_CHECKED_MAX_RETAINED_FRAMES (#35215)
7a7b414d1f is described below

commit 7a7b414d1fd0d648a6d248fb1bbe1984e9585480
Author: Herman Schaaf <[email protected]>
AuthorDate: Tue Apr 18 20:45:41 2023 +0100

    GH-35212: [Go] Add ability to show full call stack with 
ARROW_CHECKED_MAX_RETAINED_FRAMES (#35215)
    
    This adds support for printing the full call stack when a leak is reported 
by the checked memory allocator. An `ARROW_CHECKED_MAX_RETAINED_FRAMES` 
environment variable controls how many frames are retained. To keep this 
completely backwards-compatible, the default right now is zero. In this case, 
the reported error is exactly the same as before. When a higher value is given 
though, a longer call stack is printed. For example:
    
    Before (same as `ARROW_CHECKED_MAX_RETAINED_FRAMES=0`):
    
    ```
    checked_allocator.go:160: LEAK of 64 bytes FROM 
github.com/apache/arrow/go/v12/arrow/array.(*TimestampBuilder).newData line 2396
    ```
    
    After (with `ARROW_CHECKED_MAX_RETAINED_FRAMES=100`):
    
    ```
    checked_allocator.go:160: LEAK of 64 bytes FROM 
github.com/apache/arrow/go/v12/arrow/array.(*TimestampBuilder).newData line 2396
         
github.com/apache/arrow/go/v12/arrow/array.(*TimestampBuilder).NewTimestampArray
 line 2386
         
github.com/apache/arrow/go/v12/arrow/array.(*TimestampBuilder).NewArray line 
2380
         github.com/apache/arrow/go/v12/arrow/array.(*RecordBuilder).NewRecord 
line 346
         github.com/cloudquery/plugin-sdk/v2/testdata.GenTestData line 243
         github.com/cloudquery/plugin-sdk/v2/plugins/destination.testMigration 
line 53
         
github.com/cloudquery/plugin-sdk/v2/plugins/destination.(*PluginTestSuite).destinationPluginTestMigrate.func5
 line 239
         testing.tRunner line 1576
         runtime.goexit line 1172
    ```
    * Closes: #35212
    
    Authored-by: Herman Schaaf <[email protected]>
    Signed-off-by: Matt Topol <[email protected]>
---
 go/arrow/memory/checked_allocator.go | 55 ++++++++++++++++++++++++++++--------
 1 file changed, 44 insertions(+), 11 deletions(-)

diff --git a/go/arrow/memory/checked_allocator.go 
b/go/arrow/memory/checked_allocator.go
index 06be9bda30..12f92912bb 100644
--- a/go/arrow/memory/checked_allocator.go
+++ b/go/arrow/memory/checked_allocator.go
@@ -17,9 +17,11 @@
 package memory
 
 import (
+       "fmt"
        "os"
        "runtime"
        "strconv"
+       "strings"
        "sync"
        "sync/atomic"
        "unsafe"
@@ -46,8 +48,11 @@ func (a *CheckedAllocator) Allocate(size int) []byte {
        }
 
        ptr := uintptr(unsafe.Pointer(&out[0]))
+       pcs := make([]uintptr, maxRetainedFrames)
+       runtime.Callers(allocFrames, pcs)
+       callersFrames := runtime.CallersFrames(pcs)
        if pc, _, l, ok := runtime.Caller(allocFrames); ok {
-               a.allocs.Store(ptr, &dalloc{pc: pc, line: l, sz: size})
+               a.allocs.Store(ptr, &dalloc{pc: pc, line: l, sz: size, 
callersFrames: callersFrames})
        }
        return out
 }
@@ -63,9 +68,13 @@ func (a *CheckedAllocator) Reallocate(size int, b []byte) 
[]byte {
 
        newptr := uintptr(unsafe.Pointer(&out[0]))
        a.allocs.Delete(oldptr)
+       pcs := make([]uintptr, maxRetainedFrames)
+       runtime.Callers(reallocFrames, pcs)
+       callersFrames := runtime.CallersFrames(pcs)
        if pc, _, l, ok := runtime.Caller(reallocFrames); ok {
-               a.allocs.Store(newptr, &dalloc{pc: pc, line: l, sz: size})
+               a.allocs.Store(newptr, &dalloc{pc: pc, line: l, sz: size, 
callersFrames: callersFrames})
        }
+
        return out
 }
 
@@ -86,14 +95,16 @@ func (a *CheckedAllocator) Free(b []byte) {
 // of the inner workings of Buffer in order to find the caller that actually 
triggered
 // the allocation via a call to Resize/Reserve/etc.
 const (
-       defAllocFrames   = 4
-       defReallocFrames = 3
+       defAllocFrames       = 4
+       defReallocFrames     = 3
+       defMaxRetainedFrames = 0
 )
 
 // Use the environment variables ARROW_CHECKED_ALLOC_FRAMES and 
ARROW_CHECKED_REALLOC_FRAMES
-// to control how many frames up it checks when storing the caller for 
allocations/reallocs
-// when using this to find memory leaks.
-var allocFrames, reallocFrames int = defAllocFrames, defReallocFrames
+// to control how many frames it skips when storing the caller for 
allocations/reallocs
+// when using this to find memory leaks. Use ARROW_CHECKED_MAX_RETAINED_FRAMES 
to control how
+// many frames are retained for printing the stack trace of a leak.
+var allocFrames, reallocFrames, maxRetainedFrames int = defAllocFrames, 
defReallocFrames, defMaxRetainedFrames
 
 func init() {
        if val, ok := os.LookupEnv("ARROW_CHECKED_ALLOC_FRAMES"); ok {
@@ -107,12 +118,19 @@ func init() {
                        reallocFrames = f
                }
        }
+
+       if val, ok := os.LookupEnv("ARROW_CHECKED_MAX_RETAINED_FRAMES"); ok {
+               if f, err := strconv.Atoi(val); err == nil {
+                       maxRetainedFrames = f
+               }
+       }
 }
 
 type dalloc struct {
-       pc   uintptr
-       line int
-       sz   int
+       pc            uintptr
+       line          int
+       sz            int
+       callersFrames *runtime.Frames
 }
 
 type TestingT interface {
@@ -124,7 +142,22 @@ func (a *CheckedAllocator) AssertSize(t TestingT, sz int) {
        a.allocs.Range(func(_, value interface{}) bool {
                info := value.(*dalloc)
                f := runtime.FuncForPC(info.pc)
-               t.Errorf("LEAK of %d bytes FROM %s line %d\n", info.sz, 
f.Name(), info.line)
+               frames := info.callersFrames
+               var callersMsg strings.Builder
+               for {
+                       frame, more := frames.Next()
+                       if frame.Line == 0 {
+                               break
+                       }
+                       callersMsg.WriteString("\t")
+                       callersMsg.WriteString(frame.Function)
+                       callersMsg.WriteString(fmt.Sprintf(" line %d", 
frame.Line))
+                       callersMsg.WriteString("\n")
+                       if !more {
+                               break
+                       }
+               }
+               t.Errorf("LEAK of %d bytes FROM %s line %d\n%v", info.sz, 
f.Name(), info.line, callersMsg.String())
                return true
        })
 

Reply via email to