github.com/ledgerwatch/erigon-lib@v1.0.0/common/dbg/leak_detector.go (about) 1 package dbg 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/ledgerwatch/log/v3" 11 ) 12 13 const FileCloseLogLevel = log.LvlTrace 14 15 // LeakDetector - use it to find which resource was created but not closed (leaked) 16 // periodically does print in logs resources which living longer than 1min with their creation stack trace 17 // For example db transactions can call Add/Del from Begin/Commit/Rollback methods 18 type LeakDetector struct { 19 enabled atomic.Bool 20 slowThreshold atomic.Pointer[time.Duration] 21 autoIncrement atomic.Uint64 22 23 list map[uint64]LeakDetectorItem 24 listLock sync.Mutex 25 } 26 27 type LeakDetectorItem struct { 28 stack string 29 started time.Time 30 } 31 32 func NewLeakDetector(name string, slowThreshold time.Duration) *LeakDetector { 33 enabled := slowThreshold > 0 34 if !enabled { 35 return nil 36 } 37 d := &LeakDetector{list: map[uint64]LeakDetectorItem{}} 38 d.SetSlowThreshold(slowThreshold) 39 40 if enabled { 41 go func() { 42 logEvery := time.NewTicker(60 * time.Second) 43 defer logEvery.Stop() 44 45 for { 46 select { 47 case <-logEvery.C: 48 if list := d.slowList(); len(list) > 0 { 49 log.Info(fmt.Sprintf("[dbg.%s] long living resources", name), "list", strings.Join(d.slowList(), ", ")) 50 } 51 } 52 } 53 }() 54 } 55 return d 56 } 57 58 func (d *LeakDetector) slowList() (res []string) { 59 if d == nil || !d.Enabled() { 60 return res 61 } 62 slowThreshold := *d.slowThreshold.Load() 63 64 d.listLock.Lock() 65 defer d.listLock.Unlock() 66 i := 0 67 for key, value := range d.list { 68 living := time.Since(value.started) 69 if living > slowThreshold { 70 res = append(res, fmt.Sprintf("%d(%s): %s", key, living, value.stack)) 71 } 72 i++ 73 if i > 10 { // protect logs from too many output 74 break 75 } 76 } 77 return res 78 } 79 80 func (d *LeakDetector) Del(id uint64) { 81 if d == nil || !d.Enabled() { 82 return 83 } 84 d.listLock.Lock() 85 defer d.listLock.Unlock() 86 delete(d.list, id) 87 } 88 func (d *LeakDetector) Add() uint64 { 89 if d == nil || !d.Enabled() { 90 return 0 91 } 92 ac := LeakDetectorItem{ 93 stack: StackSkip(2), 94 started: time.Now(), 95 } 96 id := d.autoIncrement.Add(1) 97 d.listLock.Lock() 98 defer d.listLock.Unlock() 99 d.list[id] = ac 100 return id 101 } 102 103 func (d *LeakDetector) Enabled() bool { return d.enabled.Load() } 104 func (d *LeakDetector) SetSlowThreshold(t time.Duration) { 105 d.slowThreshold.Store(&t) 106 d.enabled.Store(t > 0) 107 }