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 }