refs_map.go raw

   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  	"gvisor.dev/gvisor/pkg/log"
  21  	"gvisor.dev/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  }
 180