github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/refs/refs_map.go (about)

     1  // Copyright 2020 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
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/sagernet/gvisor/pkg/log"
    21  	"github.com/sagernet/gvisor/pkg/sync"
    22  )
    23  
    24  var (
    25  	// liveObjects is a global map of reference-counted objects. Objects are
    26  	// inserted when leak check is enabled, and they are removed when they are
    27  	// destroyed. It is protected by liveObjectsMu.
    28  	liveObjects   map[CheckedObject]struct{}
    29  	liveObjectsMu sync.Mutex
    30  )
    31  
    32  // CheckedObject represents a reference-counted object with an informative
    33  // leak detection message.
    34  type CheckedObject interface {
    35  	// RefType is the type of the reference-counted object.
    36  	RefType() string
    37  
    38  	// LeakMessage supplies a warning to be printed upon leak detection.
    39  	LeakMessage() string
    40  
    41  	// LogRefs indicates whether reference-related events should be logged.
    42  	LogRefs() bool
    43  }
    44  
    45  func init() {
    46  	liveObjects = make(map[CheckedObject]struct{})
    47  }
    48  
    49  // LeakCheckEnabled returns whether leak checking is enabled. The following
    50  // functions should only be called if it returns true.
    51  func LeakCheckEnabled() bool {
    52  	mode := GetLeakMode()
    53  	return mode != NoLeakChecking
    54  }
    55  
    56  // leakCheckPanicEnabled returns whether DoLeakCheck() should panic when leaks
    57  // are detected.
    58  func leakCheckPanicEnabled() bool {
    59  	return GetLeakMode() == LeaksPanic
    60  }
    61  
    62  // Register adds obj to the live object map.
    63  func Register(obj CheckedObject) {
    64  	if LeakCheckEnabled() {
    65  		liveObjectsMu.Lock()
    66  		if _, ok := liveObjects[obj]; ok {
    67  			panic(fmt.Sprintf("Unexpected entry in leak checking map: reference %p already added", obj))
    68  		}
    69  		liveObjects[obj] = struct{}{}
    70  		liveObjectsMu.Unlock()
    71  		if LeakCheckEnabled() && obj.LogRefs() {
    72  			logEvent(obj, "registered")
    73  		}
    74  	}
    75  }
    76  
    77  // Unregister removes obj from the live object map.
    78  func Unregister(obj CheckedObject) {
    79  	if LeakCheckEnabled() {
    80  		liveObjectsMu.Lock()
    81  		defer liveObjectsMu.Unlock()
    82  		if _, ok := liveObjects[obj]; !ok {
    83  			panic(fmt.Sprintf("Expected to find entry in leak checking map for reference %p", obj))
    84  		}
    85  		delete(liveObjects, obj)
    86  		if LeakCheckEnabled() && obj.LogRefs() {
    87  			logEvent(obj, "unregistered")
    88  		}
    89  	}
    90  }
    91  
    92  // LogIncRef logs a reference increment.
    93  func LogIncRef(obj CheckedObject, refs int64) {
    94  	if LeakCheckEnabled() && obj.LogRefs() {
    95  		logEvent(obj, fmt.Sprintf("IncRef to %d", refs))
    96  	}
    97  }
    98  
    99  // LogTryIncRef logs a successful TryIncRef call.
   100  func LogTryIncRef(obj CheckedObject, refs int64) {
   101  	if LeakCheckEnabled() && obj.LogRefs() {
   102  		logEvent(obj, fmt.Sprintf("TryIncRef to %d", refs))
   103  	}
   104  }
   105  
   106  // LogDecRef logs a reference decrement.
   107  func LogDecRef(obj CheckedObject, refs int64) {
   108  	if LeakCheckEnabled() && obj.LogRefs() {
   109  		logEvent(obj, fmt.Sprintf("DecRef to %d", refs))
   110  	}
   111  }
   112  
   113  // logEvent logs a message for the given reference-counted object.
   114  //
   115  // obj.LogRefs() should be checked before calling logEvent, in order to avoid
   116  // calling any text processing needed to evaluate msg.
   117  func logEvent(obj CheckedObject, msg string) {
   118  	log.Infof("[%s %p] %s:\n%s", obj.RefType(), obj, msg, FormatStack(RecordStack()))
   119  }
   120  
   121  // checkOnce makes sure that leak checking is only done once. DoLeakCheck is
   122  // called from multiple places (which may overlap) to cover different sandbox
   123  // exit scenarios.
   124  var checkOnce sync.Once
   125  
   126  // DoLeakCheck iterates through the live object map and logs a message for each
   127  // object. It should be called when no reference-counted objects are reachable
   128  // anymore, at which point anything left in the map is considered a leak. On
   129  // multiple calls, only the first call will perform the leak check.
   130  func DoLeakCheck() {
   131  	if LeakCheckEnabled() {
   132  		checkOnce.Do(doLeakCheck)
   133  	}
   134  }
   135  
   136  // DoRepeatedLeakCheck is the same as DoLeakCheck except that it can be called
   137  // multiple times by the caller to incrementally perform leak checking.
   138  func DoRepeatedLeakCheck() {
   139  	if LeakCheckEnabled() {
   140  		doLeakCheck()
   141  	}
   142  }
   143  
   144  type leakCheckDisabled interface {
   145  	LeakCheckDisabled() bool
   146  }
   147  
   148  // CleanupSync is used to wait for async cleanup actions.
   149  var CleanupSync sync.WaitGroup
   150  
   151  func doLeakCheck() {
   152  	CleanupSync.Wait()
   153  	liveObjectsMu.Lock()
   154  	defer liveObjectsMu.Unlock()
   155  	leaked := len(liveObjects)
   156  	if leaked > 0 {
   157  		n := 0
   158  		msg := fmt.Sprintf("Leak checking detected %d leaked objects:\n", leaked)
   159  		for obj := range liveObjects {
   160  			skip := false
   161  			if o, ok := obj.(leakCheckDisabled); ok {
   162  				skip = o.LeakCheckDisabled()
   163  			}
   164  			if skip {
   165  				log.Debugf(obj.LeakMessage())
   166  				continue
   167  			}
   168  			msg += obj.LeakMessage() + "\n"
   169  			n++
   170  		}
   171  		if n == 0 {
   172  			return
   173  		}
   174  		if leakCheckPanicEnabled() {
   175  			panic(msg)
   176  		}
   177  		log.Warningf(msg)
   178  	}
   179  }