diff options
Diffstat (limited to 'pkg/refsvfs2')
-rw-r--r-- | pkg/refsvfs2/BUILD | 37 | ||||
-rw-r--r-- | pkg/refsvfs2/README.md | 66 | ||||
-rw-r--r-- | pkg/refsvfs2/refs_template.go | 158 | ||||
-rw-r--r-- | pkg/refsvfs2/refsvfs2_state_autogen.go | 3 |
4 files changed, 3 insertions, 261 deletions
diff --git a/pkg/refsvfs2/BUILD b/pkg/refsvfs2/BUILD deleted file mode 100644 index bfa1daa10..000000000 --- a/pkg/refsvfs2/BUILD +++ /dev/null @@ -1,37 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//tools/go_generics:defs.bzl", "go_template") - -package(licenses = ["notice"]) - -go_template( - name = "refs_template", - srcs = [ - "refs_template.go", - ], - opt_consts = [ - "logTrace", - ], - types = [ - "T", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/log", - "//pkg/refs", - ], -) - -go_library( - name = "refsvfs2", - srcs = [ - "refs.go", - "refs_map.go", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/context", - "//pkg/log", - "//pkg/refs", - "//pkg/sync", - ], -) diff --git a/pkg/refsvfs2/README.md b/pkg/refsvfs2/README.md deleted file mode 100644 index eca53c282..000000000 --- a/pkg/refsvfs2/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Reference Counting - -Go does not offer a reliable way to couple custom resource management with -object lifetime. As a result, we need to manually implement reference counting -for many objects in gVisor to make sure that resources are acquired and released -appropriately. For example, the filesystem has many reference-counted objects -(file descriptions, dentries, inodes, etc.), and it is important that each -object persists while anything holds a reference on it and is destroyed once all -references are dropped. - -We provide a template in `refs_template.go` that can be applied to most objects -in need of reference counting. It contains a simple `Refs` struct that can be -incremented and decremented, and once the reference count reaches zero, a -destructor can be called. Note that there are some objects (e.g. `gofer.dentry`, -`overlay.dentry`) that should not immediately be destroyed upon reaching zero -references; in these cases, this template cannot be applied. - -# Reference Checking - -Unfortunately, manually keeping track of reference counts is extremely error -prone, and improper accounting can lead to production bugs that are very -difficult to root cause. - -We have several ways of discovering reference count errors in gVisor. Any -attempt to increment/decrement a `Refs` struct with a count of zero will trigger -a sentry panic, since the object should have been destroyed and become -unreachable. This allows us to identify missing increments or extra decrements, -which cause the reference count to be lower than it should be: the count will -reach zero earlier than expected, and the next increment/decrement--which should -be valid--will result in a panic. - -It is trickier to identify extra increments and missing decrements, which cause -the reference count to be higher than expected (i.e. a “reference leak”). -Reference leaks prevent resources from being released properly and can translate -to various issues that are tricky to diagnose, such as memory leaks. The -following section discusses how we implement leak checking. - -## Leak Checking - -When leak checking is enabled, reference-counted objects are added to a global -map when constructed and removed when destroyed. Near the very end of sandbox -execution, once no reference-counted objects should still be reachable, we -report everything left in the map as having leaked. Leak-checking objects -implement the `CheckedObject` interface, which allows us to print informative -warnings for each of the leaked objects. - -Leak checking is provided by `refs_template`, but objects that do not use the -template will also need to implement `CheckedObject` and be manually -registered/unregistered from the map in order to be checked. - -Note that leak checking affects performance and memory usage, so it should only -be enabled in testing environments. - -## Debugging - -Even with the checks described above, it can be difficult to track down the -exact source of a reference counting error. The error may occur far before it is -discovered (for instance, a missing `IncRef` may not be discovered until a -future `DecRef` makes the count negative). To aid in debugging, `refs_template` -provides the `enableLogging` option to log every `IncRef`, `DecRef`, and leak -check registration/unregistration, along with the object address and a call -stack. This allows us to search a log for all of the changes to a particular -object's reference count, which makes it much easier to identify the absent or -extraneous operation(s). The reference-counted objects that do not use -`refs_template` also provide logging, and others defined in the future should do -so as well. diff --git a/pkg/refsvfs2/refs_template.go b/pkg/refsvfs2/refs_template.go deleted file mode 100644 index f64b6c6ae..000000000 --- a/pkg/refsvfs2/refs_template.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package refs_template defines a template that can be used by reference -// counted objects. -package refs_template - -import ( - "fmt" - "sync/atomic" - - "gvisor.dev/gvisor/pkg/refsvfs2" -) - -// enableLogging indicates whether reference-related events should be logged (with -// stack traces). This is false by default and should only be set to true for -// debugging purposes, as it can generate an extremely large amount of output -// and drastically degrade performance. -const enableLogging = false - -// T is the type of the reference counted object. It is only used to customize -// debug output when leak checking. -type T interface{} - -// obj is used to customize logging. Note that we use a pointer to T so that -// we do not copy the entire object when passed as a format parameter. -var obj *T - -// Refs implements refs.RefCounter. It keeps a reference count using atomic -// operations and calls the destructor when the count reaches zero. -// -// +stateify savable -type Refs struct { - // refCount is composed of two fields: - // - // [32-bit speculative references]:[32-bit real references] - // - // Speculative references are used for TryIncRef, to avoid a CompareAndSwap - // loop. See IncRef, DecRef and TryIncRef for details of how these fields are - // used. - refCount int64 -} - -// InitRefs initializes r with one reference and, if enabled, activates leak -// checking. -func (r *Refs) InitRefs() { - atomic.StoreInt64(&r.refCount, 1) - refsvfs2.Register(r) -} - -// RefType implements refsvfs2.CheckedObject.RefType. -func (r *Refs) RefType() string { - return fmt.Sprintf("%T", obj)[1:] -} - -// LeakMessage implements refsvfs2.CheckedObject.LeakMessage. -func (r *Refs) LeakMessage() string { - return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs()) -} - -// LogRefs implements refsvfs2.CheckedObject.LogRefs. -func (r *Refs) LogRefs() bool { - return enableLogging -} - -// EnableLeakCheck enables reference leak checking on r. -func (r *Refs) EnableLeakCheck() { - refsvfs2.Register(r) -} - -// ReadRefs returns the current number of references. The returned count is -// inherently racy and is unsafe to use without external synchronization. -func (r *Refs) ReadRefs() int64 { - return atomic.LoadInt64(&r.refCount) -} - -// IncRef implements refs.RefCounter.IncRef. -// -//go:nosplit -func (r *Refs) IncRef() { - v := atomic.AddInt64(&r.refCount, 1) - if enableLogging { - refsvfs2.LogIncRef(r, v) - } - if v <= 1 { - panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType())) - } -} - -// TryIncRef implements refs.RefCounter.TryIncRef. -// -// To do this safely without a loop, a speculative reference is first acquired -// on the object. This allows multiple concurrent TryIncRef calls to distinguish -// other TryIncRef calls from genuine references held. -// -//go:nosplit -func (r *Refs) TryIncRef() bool { - const speculativeRef = 1 << 32 - if v := atomic.AddInt64(&r.refCount, speculativeRef); int32(v) == 0 { - // This object has already been freed. - atomic.AddInt64(&r.refCount, -speculativeRef) - return false - } - - // Turn into a real reference. - v := atomic.AddInt64(&r.refCount, -speculativeRef+1) - if enableLogging { - refsvfs2.LogTryIncRef(r, v) - } - return true -} - -// DecRef implements refs.RefCounter.DecRef. -// -// Note that speculative references are counted here. Since they were added -// prior to real references reaching zero, they will successfully convert to -// real references. In other words, we see speculative references only in the -// following case: -// -// A: TryIncRef [speculative increase => sees non-negative references] -// B: DecRef [real decrease] -// A: TryIncRef [transform speculative to real] -// -//go:nosplit -func (r *Refs) DecRef(destroy func()) { - v := atomic.AddInt64(&r.refCount, -1) - if enableLogging { - refsvfs2.LogDecRef(r, v+1) - } - switch { - case v < 0: - panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType())) - - case v == 0: - refsvfs2.Unregister(r) - // Call the destructor. - if destroy != nil { - destroy() - } - } -} - -func (r *Refs) afterLoad() { - if r.ReadRefs() > 0 { - r.EnableLeakCheck() - } -} diff --git a/pkg/refsvfs2/refsvfs2_state_autogen.go b/pkg/refsvfs2/refsvfs2_state_autogen.go new file mode 100644 index 000000000..ca5fbb104 --- /dev/null +++ b/pkg/refsvfs2/refsvfs2_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package refsvfs2 |