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