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  }