github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/refs/refcounter.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package refs defines an interface for reference counted objects.
    16  package refs
    17  
    18  import (
    19  	"bytes"
    20  	"fmt"
    21  	"runtime"
    22  
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/atomicbitops"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    26  )
    27  
    28  // RefCounter is the interface to be implemented by objects that are reference
    29  // counted.
    30  type RefCounter interface {
    31  	// IncRef increments the reference counter on the object.
    32  	IncRef()
    33  
    34  	// DecRef decrements the object's reference count. Users of refs_template.Refs
    35  	// may specify a destructor to be called once the reference count reaches zero.
    36  	DecRef(ctx context.Context)
    37  }
    38  
    39  // TryRefCounter is like RefCounter but allow the ref increment to be tried.
    40  type TryRefCounter interface {
    41  	RefCounter
    42  
    43  	// TryIncRef attempts to increment the reference count, but may fail if all
    44  	// references have already been dropped, in which case it returns false. If
    45  	// true is returned, then a valid reference is now held on the object.
    46  	TryIncRef() bool
    47  }
    48  
    49  // LeakMode configures the leak checker.
    50  type LeakMode uint32
    51  
    52  const (
    53  	// NoLeakChecking indicates that no effort should be made to check for
    54  	// leaks.
    55  	NoLeakChecking LeakMode = iota
    56  
    57  	// LeaksLogWarning indicates that a warning should be logged when leaks
    58  	// are found.
    59  	LeaksLogWarning
    60  
    61  	// LeaksPanic indidcates that a panic should be issued when leaks are found.
    62  	LeaksPanic
    63  )
    64  
    65  // Set implements flag.Value.
    66  func (l *LeakMode) Set(v string) error {
    67  	switch v {
    68  	case "disabled":
    69  		*l = NoLeakChecking
    70  	case "log-names":
    71  		*l = LeaksLogWarning
    72  	case "panic":
    73  		*l = LeaksPanic
    74  	default:
    75  		return fmt.Errorf("invalid ref leak mode %q", v)
    76  	}
    77  	return nil
    78  }
    79  
    80  // Get implements flag.Value.
    81  func (l *LeakMode) Get() any {
    82  	return *l
    83  }
    84  
    85  // String implements flag.Value.
    86  func (l LeakMode) String() string {
    87  	switch l {
    88  	case NoLeakChecking:
    89  		return "disabled"
    90  	case LeaksLogWarning:
    91  		return "log-names"
    92  	case LeaksPanic:
    93  		return "panic"
    94  	default:
    95  		panic(fmt.Sprintf("invalid ref leak mode %d", l))
    96  	}
    97  }
    98  
    99  // leakMode stores the current mode for the reference leak checker.
   100  //
   101  // Values must be one of the LeakMode values.
   102  //
   103  // leakMode must be accessed atomically.
   104  var leakMode atomicbitops.Uint32
   105  
   106  // SetLeakMode configures the reference leak checker.
   107  func SetLeakMode(mode LeakMode) {
   108  	leakMode.Store(uint32(mode))
   109  }
   110  
   111  // GetLeakMode returns the current leak mode.
   112  func GetLeakMode() LeakMode {
   113  	return LeakMode(leakMode.Load())
   114  }
   115  
   116  const maxStackFrames = 40
   117  
   118  type fileLine struct {
   119  	file string
   120  	line int
   121  }
   122  
   123  // A stackKey is a representation of a stack frame for use as a map key.
   124  //
   125  // The fileLine type is used as PC values seem to vary across collections, even
   126  // for the same call stack.
   127  type stackKey [maxStackFrames]fileLine
   128  
   129  var stackCache = struct {
   130  	sync.Mutex
   131  	entries map[stackKey][]uintptr
   132  }{entries: map[stackKey][]uintptr{}}
   133  
   134  func makeStackKey(pcs []uintptr) stackKey {
   135  	frames := runtime.CallersFrames(pcs)
   136  	var key stackKey
   137  	keySlice := key[:0]
   138  	for {
   139  		frame, more := frames.Next()
   140  		keySlice = append(keySlice, fileLine{frame.File, frame.Line})
   141  
   142  		if !more || len(keySlice) == len(key) {
   143  			break
   144  		}
   145  	}
   146  	return key
   147  }
   148  
   149  // RecordStack constructs and returns the PCs on the current stack.
   150  func RecordStack() []uintptr {
   151  	pcs := make([]uintptr, maxStackFrames)
   152  	n := runtime.Callers(1, pcs)
   153  	if n == 0 {
   154  		// No pcs available. Stop now.
   155  		//
   156  		// This can happen if the first argument to runtime.Callers
   157  		// is large.
   158  		return nil
   159  	}
   160  	pcs = pcs[:n]
   161  	key := makeStackKey(pcs)
   162  	stackCache.Lock()
   163  	v, ok := stackCache.entries[key]
   164  	if !ok {
   165  		// Reallocate to prevent pcs from escaping.
   166  		v = append([]uintptr(nil), pcs...)
   167  		stackCache.entries[key] = v
   168  	}
   169  	stackCache.Unlock()
   170  	return v
   171  }
   172  
   173  // FormatStack converts the given stack into a readable format.
   174  func FormatStack(pcs []uintptr) string {
   175  	frames := runtime.CallersFrames(pcs)
   176  	var trace bytes.Buffer
   177  	for {
   178  		frame, more := frames.Next()
   179  		fmt.Fprintf(&trace, "%s:%d: %s\n", frame.File, frame.Line, frame.Function)
   180  
   181  		if !more {
   182  			break
   183  		}
   184  	}
   185  	return trace.String()
   186  }
   187  
   188  // OnExit is called on sandbox exit. It runs GC to enqueue refcount finalizers,
   189  // which check for reference leaks. There is no way to guarantee that every
   190  // finalizer will run before exiting, but this at least ensures that they will
   191  // be discovered/enqueued by GC.
   192  func OnExit() {
   193  	if LeakMode(leakMode.Load()) != NoLeakChecking {
   194  		runtime.GC()
   195  	}
   196  }