gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/refs/refs_template.go (about)

     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_template defines a template that can be used by reference
    16  // counted objects. The template comes with leak checking capabilities.
    17  package refs_template
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"gvisor.dev/gvisor/pkg/atomicbitops"
    24  	"gvisor.dev/gvisor/pkg/refs"
    25  )
    26  
    27  // enableLogging indicates whether reference-related events should be logged (with
    28  // stack traces). This is false by default and should only be set to true for
    29  // debugging purposes, as it can generate an extremely large amount of output
    30  // and drastically degrade performance.
    31  const enableLogging = false
    32  
    33  // T is the type of the reference counted object. It is only used to customize
    34  // debug output when leak checking.
    35  type T any
    36  
    37  // obj is used to customize logging. Note that we use a pointer to T so that
    38  // we do not copy the entire object when passed as a format parameter.
    39  var obj *T
    40  
    41  // Refs implements refs.RefCounter. It keeps a reference count using atomic
    42  // operations and calls the destructor when the count reaches zero.
    43  //
    44  // NOTE: Do not introduce additional fields to the Refs struct. It is used by
    45  // many filesystem objects, and we want to keep it as small as possible (i.e.,
    46  // the same size as using an int64 directly) to avoid taking up extra cache
    47  // space. In general, this template should not be extended at the cost of
    48  // performance. If it does not offer enough flexibility for a particular object
    49  // (example: b/187877947), we should implement the RefCounter/CheckedObject
    50  // interfaces manually.
    51  //
    52  // +stateify savable
    53  type Refs struct {
    54  	// refCount is composed of two fields:
    55  	//
    56  	//	[32-bit speculative references]:[32-bit real references]
    57  	//
    58  	// Speculative references are used for TryIncRef, to avoid a CompareAndSwap
    59  	// loop. See IncRef, DecRef and TryIncRef for details of how these fields are
    60  	// used.
    61  	refCount atomicbitops.Int64
    62  }
    63  
    64  // InitRefs initializes r with one reference and, if enabled, activates leak
    65  // checking.
    66  func (r *Refs) InitRefs() {
    67  	// We can use RacyStore because the refs can't be shared until after
    68  	// InitRefs is called, and thus it's safe to use non-atomic operations.
    69  	r.refCount.RacyStore(1)
    70  	refs.Register(r)
    71  }
    72  
    73  // RefType implements refs.CheckedObject.RefType.
    74  func (r *Refs) RefType() string {
    75  	return fmt.Sprintf("%T", obj)[1:]
    76  }
    77  
    78  // LeakMessage implements refs.CheckedObject.LeakMessage.
    79  func (r *Refs) LeakMessage() string {
    80  	return fmt.Sprintf("[%s %p] reference count of %d instead of 0", r.RefType(), r, r.ReadRefs())
    81  }
    82  
    83  // LogRefs implements refs.CheckedObject.LogRefs.
    84  func (r *Refs) LogRefs() bool {
    85  	return enableLogging
    86  }
    87  
    88  // ReadRefs returns the current number of references. The returned count is
    89  // inherently racy and is unsafe to use without external synchronization.
    90  func (r *Refs) ReadRefs() int64 {
    91  	return r.refCount.Load()
    92  }
    93  
    94  // IncRef implements refs.RefCounter.IncRef.
    95  //
    96  //go:nosplit
    97  func (r *Refs) IncRef() {
    98  	v := r.refCount.Add(1)
    99  	if enableLogging {
   100  		refs.LogIncRef(r, v)
   101  	}
   102  	if v <= 1 {
   103  		panic(fmt.Sprintf("Incrementing non-positive count %p on %s", r, r.RefType()))
   104  	}
   105  }
   106  
   107  // TryIncRef implements refs.TryRefCounter.TryIncRef.
   108  //
   109  // To do this safely without a loop, a speculative reference is first acquired
   110  // on the object. This allows multiple concurrent TryIncRef calls to distinguish
   111  // other TryIncRef calls from genuine references held.
   112  //
   113  //go:nosplit
   114  func (r *Refs) TryIncRef() bool {
   115  	const speculativeRef = 1 << 32
   116  	if v := r.refCount.Add(speculativeRef); int32(v) == 0 {
   117  		// This object has already been freed.
   118  		r.refCount.Add(-speculativeRef)
   119  		return false
   120  	}
   121  
   122  	// Turn into a real reference.
   123  	v := r.refCount.Add(-speculativeRef + 1)
   124  	if enableLogging {
   125  		refs.LogTryIncRef(r, v)
   126  	}
   127  	return true
   128  }
   129  
   130  // DecRef implements refs.RefCounter.DecRef.
   131  //
   132  // Note that speculative references are counted here. Since they were added
   133  // prior to real references reaching zero, they will successfully convert to
   134  // real references. In other words, we see speculative references only in the
   135  // following case:
   136  //
   137  //	A: TryIncRef [speculative increase => sees non-negative references]
   138  //	B: DecRef [real decrease]
   139  //	A: TryIncRef [transform speculative to real]
   140  //
   141  //go:nosplit
   142  func (r *Refs) DecRef(destroy func()) {
   143  	v := r.refCount.Add(-1)
   144  	if enableLogging {
   145  		refs.LogDecRef(r, v)
   146  	}
   147  	switch {
   148  	case v < 0:
   149  		panic(fmt.Sprintf("Decrementing non-positive ref count %p, owned by %s", r, r.RefType()))
   150  
   151  	case v == 0:
   152  		refs.Unregister(r)
   153  		// Call the destructor.
   154  		if destroy != nil {
   155  			destroy()
   156  		}
   157  	}
   158  }
   159  
   160  func (r *Refs) afterLoad(context.Context) {
   161  	if r.ReadRefs() > 0 {
   162  		refs.Register(r)
   163  	}
   164  }