summaryrefslogtreecommitdiffhomepage
path: root/pkg/refs
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/refs')
-rw-r--r--pkg/refs/BUILD38
-rw-r--r--pkg/refs/refcounter.go469
-rw-r--r--pkg/refs/refcounter_state.go35
-rw-r--r--pkg/refs/refcounter_test.go173
4 files changed, 715 insertions, 0 deletions
diff --git a/pkg/refs/BUILD b/pkg/refs/BUILD
new file mode 100644
index 000000000..74affc887
--- /dev/null
+++ b/pkg/refs/BUILD
@@ -0,0 +1,38 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools/go_generics:defs.bzl", "go_template_instance")
+
+package(licenses = ["notice"])
+
+go_template_instance(
+ name = "weak_ref_list",
+ out = "weak_ref_list.go",
+ package = "refs",
+ prefix = "weakRef",
+ template = "//pkg/ilist:generic_list",
+ types = {
+ "Element": "*WeakRef",
+ "Linker": "*WeakRef",
+ },
+)
+
+go_library(
+ name = "refs",
+ srcs = [
+ "refcounter.go",
+ "refcounter_state.go",
+ "weak_ref_list.go",
+ ],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/log",
+ "//pkg/sync",
+ ],
+)
+
+go_test(
+ name = "refs_test",
+ size = "small",
+ srcs = ["refcounter_test.go"],
+ library = ":refs",
+ deps = ["//pkg/sync"],
+)
diff --git a/pkg/refs/refcounter.go b/pkg/refs/refcounter.go
new file mode 100644
index 000000000..c45ba8200
--- /dev/null
+++ b/pkg/refs/refcounter.go
@@ -0,0 +1,469 @@
+// Copyright 2018 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 defines an interface for reference counted objects. It
+// also provides a drop-in implementation called AtomicRefCount.
+package refs
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "runtime"
+ "sync/atomic"
+
+ "gvisor.dev/gvisor/pkg/log"
+ "gvisor.dev/gvisor/pkg/sync"
+)
+
+// RefCounter is the interface to be implemented by objects that are reference
+// counted.
+type RefCounter interface {
+ // IncRef increments the reference counter on the object.
+ IncRef()
+
+ // DecRef decrements the reference counter on the object.
+ //
+ // Note that AtomicRefCounter.DecRef() does not support destructors.
+ // If a type has a destructor, it must implement its own DecRef()
+ // method and call AtomicRefCounter.DecRefWithDestructor(destructor).
+ DecRef()
+
+ // TryIncRef attempts to increase the reference counter on the object,
+ // but may fail if all references have already been dropped. This
+ // should be used only in special circumstances, such as WeakRefs.
+ TryIncRef() bool
+
+ // addWeakRef adds the given weak reference. Note that you should have a
+ // reference to the object when calling this method.
+ addWeakRef(*WeakRef)
+
+ // dropWeakRef drops the given weak reference. Note that you should have
+ // a reference to the object when calling this method.
+ dropWeakRef(*WeakRef)
+}
+
+// A WeakRefUser is notified when the last non-weak reference is dropped.
+type WeakRefUser interface {
+ // WeakRefGone is called when the last non-weak reference is dropped.
+ WeakRefGone()
+}
+
+// WeakRef is a weak reference.
+//
+// +stateify savable
+type WeakRef struct {
+ weakRefEntry `state:"nosave"`
+
+ // obj is an atomic value that points to the refCounter.
+ obj atomic.Value `state:".(savedReference)"`
+
+ // user is notified when the weak ref is zapped by the object getting
+ // destroyed.
+ user WeakRefUser
+}
+
+// weakRefPool is a pool of weak references to avoid allocations on the hot path.
+var weakRefPool = sync.Pool{
+ New: func() interface{} {
+ return &WeakRef{}
+ },
+}
+
+// NewWeakRef acquires a weak reference for the given object.
+//
+// An optional user will be notified when the last non-weak reference is
+// dropped.
+//
+// Note that you must hold a reference to the object prior to getting a weak
+// reference. (But you may drop the non-weak reference after that.)
+func NewWeakRef(rc RefCounter, u WeakRefUser) *WeakRef {
+ w := weakRefPool.Get().(*WeakRef)
+ w.init(rc, u)
+ return w
+}
+
+// get attempts to get a normal reference to the underlying object, and returns
+// the object. If this weak reference has already been zapped (the object has
+// been destroyed) then false is returned. If the object still exists, then
+// true is returned.
+func (w *WeakRef) get() (RefCounter, bool) {
+ rc := w.obj.Load().(RefCounter)
+ if v := reflect.ValueOf(rc); v == reflect.Zero(v.Type()) {
+ // This pointer has already been zapped by zap() below. We do
+ // this to ensure that the GC can collect the underlying
+ // RefCounter objects and they don't hog resources.
+ return nil, false
+ }
+ if !rc.TryIncRef() {
+ return nil, true
+ }
+ return rc, true
+}
+
+// Get attempts to get a normal reference to the underlying object, and returns
+// the object. If this fails (the object no longer exists), then nil will be
+// returned instead.
+func (w *WeakRef) Get() RefCounter {
+ rc, _ := w.get()
+ return rc
+}
+
+// Drop drops this weak reference. You should always call drop when you are
+// finished with the weak reference. You may not use this object after calling
+// drop.
+func (w *WeakRef) Drop() {
+ rc, ok := w.get()
+ if !ok {
+ // We've been zapped already. When the refcounter has called
+ // zap, we're guaranteed it's not holding references.
+ weakRefPool.Put(w)
+ return
+ }
+ if rc == nil {
+ // The object is in the process of being destroyed. We can't
+ // remove this from the object's list, nor can we return this
+ // object to the pool. It'll just be garbage collected. This is
+ // a rare edge case, so it's not a big deal.
+ return
+ }
+
+ // At this point, we have a reference on the object. So destruction
+ // of the object (and zapping this weak reference) can't race here.
+ rc.dropWeakRef(w)
+
+ // And now aren't on the object's list of weak references. So it won't
+ // zap us if this causes the reference count to drop to zero.
+ rc.DecRef()
+
+ // Return to the pool.
+ weakRefPool.Put(w)
+}
+
+// init initializes this weak reference.
+func (w *WeakRef) init(rc RefCounter, u WeakRefUser) {
+ // Reset the contents of the weak reference.
+ // This is important because we are reseting the atomic value type.
+ // Otherwise, we could panic here if obj is different than what it was
+ // the last time this was used.
+ *w = WeakRef{}
+ w.user = u
+ w.obj.Store(rc)
+
+ // In the load path, we may already have a nil value. So we need to
+ // check whether or not that is the case before calling addWeakRef.
+ if v := reflect.ValueOf(rc); v != reflect.Zero(v.Type()) {
+ rc.addWeakRef(w)
+ }
+}
+
+// zap zaps this weak reference.
+func (w *WeakRef) zap() {
+ // We need to be careful about types here.
+ // So reflect is involved. But it's not that bad.
+ rc := w.obj.Load()
+ typ := reflect.TypeOf(rc)
+ w.obj.Store(reflect.Zero(typ).Interface())
+}
+
+// AtomicRefCount keeps a reference count using atomic operations and calls the
+// destructor when the count reaches zero.
+//
+// N.B. To allow the zero-object to be initialized, the count is offset by
+// 1, that is, when refCount is n, there are really n+1 references.
+//
+// +stateify savable
+type AtomicRefCount 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
+
+ // name is the name of the type which owns this ref count.
+ //
+ // name is immutable after EnableLeakCheck is called.
+ name string
+
+ // stack optionally records the caller of EnableLeakCheck.
+ //
+ // stack is immutable after EnableLeakCheck is called.
+ stack []uintptr
+
+ // mu protects the list below.
+ mu sync.Mutex `state:"nosave"`
+
+ // weakRefs is our collection of weak references.
+ weakRefs weakRefList `state:"nosave"`
+}
+
+// LeakMode configures the leak checker.
+type LeakMode uint32
+
+const (
+ // UninitializedLeakChecking indicates that the leak checker has not yet been initialized.
+ UninitializedLeakChecking LeakMode = iota
+
+ // NoLeakChecking indicates that no effort should be made to check for
+ // leaks.
+ NoLeakChecking
+
+ // LeaksLogWarning indicates that a warning should be logged when leaks
+ // are found.
+ LeaksLogWarning
+
+ // LeaksLogTraces indicates that a trace collected during allocation
+ // should be logged when leaks are found.
+ LeaksLogTraces
+)
+
+// leakMode stores the current mode for the reference leak checker.
+//
+// Values must be one of the LeakMode values.
+//
+// leakMode must be accessed atomically.
+var leakMode uint32
+
+// SetLeakMode configures the reference leak checker.
+func SetLeakMode(mode LeakMode) {
+ atomic.StoreUint32(&leakMode, uint32(mode))
+}
+
+const maxStackFrames = 40
+
+type fileLine struct {
+ file string
+ line int
+}
+
+// A stackKey is a representation of a stack frame for use as a map key.
+//
+// The fileLine type is used as PC values seem to vary across collections, even
+// for the same call stack.
+type stackKey [maxStackFrames]fileLine
+
+var stackCache = struct {
+ sync.Mutex
+ entries map[stackKey][]uintptr
+}{entries: map[stackKey][]uintptr{}}
+
+func makeStackKey(pcs []uintptr) stackKey {
+ frames := runtime.CallersFrames(pcs)
+ var key stackKey
+ keySlice := key[:0]
+ for {
+ frame, more := frames.Next()
+ keySlice = append(keySlice, fileLine{frame.File, frame.Line})
+
+ if !more || len(keySlice) == len(key) {
+ break
+ }
+ }
+ return key
+}
+
+func recordStack() []uintptr {
+ pcs := make([]uintptr, maxStackFrames)
+ n := runtime.Callers(1, pcs)
+ if n == 0 {
+ // No pcs available. Stop now.
+ //
+ // This can happen if the first argument to runtime.Callers
+ // is large.
+ return nil
+ }
+ pcs = pcs[:n]
+ key := makeStackKey(pcs)
+ stackCache.Lock()
+ v, ok := stackCache.entries[key]
+ if !ok {
+ // Reallocate to prevent pcs from escaping.
+ v = append([]uintptr(nil), pcs...)
+ stackCache.entries[key] = v
+ }
+ stackCache.Unlock()
+ return v
+}
+
+func formatStack(pcs []uintptr) string {
+ frames := runtime.CallersFrames(pcs)
+ var trace bytes.Buffer
+ for {
+ frame, more := frames.Next()
+ fmt.Fprintf(&trace, "%s:%d: %s\n", frame.File, frame.Line, frame.Function)
+
+ if !more {
+ break
+ }
+ }
+ return trace.String()
+}
+
+func (r *AtomicRefCount) finalize() {
+ var note string
+ switch LeakMode(atomic.LoadUint32(&leakMode)) {
+ case NoLeakChecking:
+ return
+ case UninitializedLeakChecking:
+ note = "(Leak checker uninitialized): "
+ }
+ if n := r.ReadRefs(); n != 0 {
+ msg := fmt.Sprintf("%sAtomicRefCount %p owned by %q garbage collected with ref count of %d (want 0)", note, r, r.name, n)
+ if len(r.stack) != 0 {
+ msg += ":\nCaller:\n" + formatStack(r.stack)
+ } else {
+ msg += " (enable trace logging to debug)"
+ }
+ log.Warningf(msg)
+ }
+}
+
+// EnableLeakCheck checks for reference leaks when the AtomicRefCount gets
+// garbage collected.
+//
+// This function adds a finalizer to the AtomicRefCount, so the AtomicRefCount
+// must be at the beginning of its parent.
+//
+// name is a friendly name that will be listed as the owner of the
+// AtomicRefCount in logs. It should be the name of the parent type, including
+// package.
+func (r *AtomicRefCount) EnableLeakCheck(name string) {
+ if name == "" {
+ panic("invalid name")
+ }
+ switch LeakMode(atomic.LoadUint32(&leakMode)) {
+ case NoLeakChecking:
+ return
+ case LeaksLogTraces:
+ r.stack = recordStack()
+ }
+ r.name = name
+ runtime.SetFinalizer(r, (*AtomicRefCount).finalize)
+}
+
+// ReadRefs returns the current number of references. The returned count is
+// inherently racy and is unsafe to use without external synchronization.
+func (r *AtomicRefCount) ReadRefs() int64 {
+ // Account for the internal -1 offset on refcounts.
+ return atomic.LoadInt64(&r.refCount) + 1
+}
+
+// IncRef increments this object's reference count. While the count is kept
+// greater than zero, the destructor doesn't get called.
+//
+// The sanity check here is limited to real references, since if they have
+// dropped beneath zero then the object should have been destroyed.
+//
+//go:nosplit
+func (r *AtomicRefCount) IncRef() {
+ if v := atomic.AddInt64(&r.refCount, 1); v <= 0 {
+ panic("Incrementing non-positive ref count")
+ }
+}
+
+// TryIncRef attempts to increment the reference count, *unless the count has
+// already reached zero*. If false is returned, then the object has already
+// been destroyed, and the weak reference is no longer valid. If true if
+// returned then a valid reference is now held on the object.
+//
+// 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 *AtomicRefCount) TryIncRef() bool {
+ const speculativeRef = 1 << 32
+ v := atomic.AddInt64(&r.refCount, speculativeRef)
+ if int32(v) < 0 {
+ // This object has already been freed.
+ atomic.AddInt64(&r.refCount, -speculativeRef)
+ return false
+ }
+
+ // Turn into a real reference.
+ atomic.AddInt64(&r.refCount, -speculativeRef+1)
+ return true
+}
+
+// addWeakRef adds the given weak reference.
+func (r *AtomicRefCount) addWeakRef(w *WeakRef) {
+ r.mu.Lock()
+ r.weakRefs.PushBack(w)
+ r.mu.Unlock()
+}
+
+// dropWeakRef drops the given weak reference.
+func (r *AtomicRefCount) dropWeakRef(w *WeakRef) {
+ r.mu.Lock()
+ r.weakRefs.Remove(w)
+ r.mu.Unlock()
+}
+
+// DecRefWithDestructor decrements the object's reference count. If the
+// resulting count is negative and the destructor is not nil, then the
+// destructor will be called.
+//
+// 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 *AtomicRefCount) DecRefWithDestructor(destroy func()) {
+ switch v := atomic.AddInt64(&r.refCount, -1); {
+ case v < -1:
+ panic("Decrementing non-positive ref count")
+
+ case v == -1:
+ // Zap weak references. Note that at this point, all weak
+ // references are already invalid. That is, TryIncRef() will
+ // return false due to the reference count check.
+ r.mu.Lock()
+ for !r.weakRefs.Empty() {
+ w := r.weakRefs.Front()
+ // Capture the callback because w cannot be touched
+ // after it's zapped -- the owner is free it reuse it
+ // after that.
+ user := w.user
+ r.weakRefs.Remove(w)
+ w.zap()
+
+ if user != nil {
+ r.mu.Unlock()
+ user.WeakRefGone()
+ r.mu.Lock()
+ }
+ }
+ r.mu.Unlock()
+
+ // Call the destructor.
+ if destroy != nil {
+ destroy()
+ }
+ }
+}
+
+// DecRef decrements this object's reference count.
+//
+//go:nosplit
+func (r *AtomicRefCount) DecRef() {
+ r.DecRefWithDestructor(nil)
+}
diff --git a/pkg/refs/refcounter_state.go b/pkg/refs/refcounter_state.go
new file mode 100644
index 000000000..7c99fd2b5
--- /dev/null
+++ b/pkg/refs/refcounter_state.go
@@ -0,0 +1,35 @@
+// Copyright 2018 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
+
+// +stateify savable
+type savedReference struct {
+ obj interface{}
+}
+
+func (w *WeakRef) saveObj() savedReference {
+ // We load the object directly, because it is typed. This will be
+ // serialized and loaded as a typed value.
+ return savedReference{w.obj.Load()}
+}
+
+func (w *WeakRef) loadObj(v savedReference) {
+ // See note above. This will be serialized and loaded typed. So we're okay
+ // as long as refs aren't changing during save and load (which they should
+ // not be).
+ //
+ // w.user is loaded before loadObj is called.
+ w.init(v.obj.(RefCounter), w.user)
+}
diff --git a/pkg/refs/refcounter_test.go b/pkg/refs/refcounter_test.go
new file mode 100644
index 000000000..1ab4a4440
--- /dev/null
+++ b/pkg/refs/refcounter_test.go
@@ -0,0 +1,173 @@
+// Copyright 2018 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
+
+import (
+ "reflect"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/sync"
+)
+
+type testCounter struct {
+ AtomicRefCount
+
+ // mu protects the boolean below.
+ mu sync.Mutex
+
+ // destroyed indicates whether this was destroyed.
+ destroyed bool
+}
+
+func (t *testCounter) DecRef() {
+ t.AtomicRefCount.DecRefWithDestructor(t.destroy)
+}
+
+func (t *testCounter) destroy() {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.destroyed = true
+}
+
+func (t *testCounter) IsDestroyed() bool {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ return t.destroyed
+}
+
+func newTestCounter() *testCounter {
+ return &testCounter{destroyed: false}
+}
+
+func TestOneRef(t *testing.T) {
+ tc := newTestCounter()
+ tc.DecRef()
+
+ if !tc.IsDestroyed() {
+ t.Errorf("object should have been destroyed")
+ }
+}
+
+func TestTwoRefs(t *testing.T) {
+ tc := newTestCounter()
+ tc.IncRef()
+ tc.DecRef()
+ tc.DecRef()
+
+ if !tc.IsDestroyed() {
+ t.Errorf("object should have been destroyed")
+ }
+}
+
+func TestMultiRefs(t *testing.T) {
+ tc := newTestCounter()
+ tc.IncRef()
+ tc.DecRef()
+
+ tc.IncRef()
+ tc.DecRef()
+
+ tc.DecRef()
+
+ if !tc.IsDestroyed() {
+ t.Errorf("object should have been destroyed")
+ }
+}
+
+func TestWeakRef(t *testing.T) {
+ tc := newTestCounter()
+ w := NewWeakRef(tc, nil)
+
+ // Try resolving.
+ if x := w.Get(); x == nil {
+ t.Errorf("weak reference didn't resolve: expected %v, got nil", tc)
+ } else {
+ x.DecRef()
+ }
+
+ // Try resolving again.
+ if x := w.Get(); x == nil {
+ t.Errorf("weak reference didn't resolve: expected %v, got nil", tc)
+ } else {
+ x.DecRef()
+ }
+
+ // Shouldn't be destroyed yet. (Can't continue if this fails.)
+ if tc.IsDestroyed() {
+ t.Fatalf("original object destroyed earlier than expected")
+ }
+
+ // Drop the original reference.
+ tc.DecRef()
+
+ // Assert destroyed.
+ if !tc.IsDestroyed() {
+ t.Errorf("original object not destroyed as expected")
+ }
+
+ // Shouldn't be anything.
+ if x := w.Get(); x != nil {
+ t.Errorf("weak reference resolved: expected nil, got %v", x)
+ }
+}
+
+func TestWeakRefDrop(t *testing.T) {
+ tc := newTestCounter()
+ w := NewWeakRef(tc, nil)
+ w.Drop()
+
+ // Just assert the list is empty.
+ if !tc.weakRefs.Empty() {
+ t.Errorf("weak reference not dropped")
+ }
+
+ // Drop the original reference.
+ tc.DecRef()
+}
+
+type testWeakRefUser struct {
+ weakRefGone func()
+}
+
+func (u *testWeakRefUser) WeakRefGone() {
+ u.weakRefGone()
+}
+
+func TestCallback(t *testing.T) {
+ called := false
+ tc := newTestCounter()
+ var w *WeakRef
+ w = NewWeakRef(tc, &testWeakRefUser{func() {
+ called = true
+
+ // Check that the weak ref has been zapped.
+ rc := w.obj.Load().(RefCounter)
+ if v := reflect.ValueOf(rc); v != reflect.Zero(v.Type()) {
+ t.Fatalf("Callback called with non-nil ptr")
+ }
+
+ // Check that we're not holding the mutex by acquiring and
+ // releasing it.
+ tc.mu.Lock()
+ tc.mu.Unlock()
+ }})
+
+ // Drop the original reference, this must trigger the callback.
+ tc.DecRef()
+
+ if !called {
+ t.Fatalf("Callback not called")
+ }
+}