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