github.com/m3db/m3@v1.5.0/src/x/checked/ref.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package checked
    22  
    23  import (
    24  	"fmt"
    25  	"reflect"
    26  	"runtime"
    27  	"sync"
    28  	"sync/atomic"
    29  	"unsafe"
    30  
    31  	xresource "github.com/m3db/m3/src/x/resource"
    32  )
    33  
    34  // RefCount is an embeddable checked.Ref.
    35  type RefCount struct {
    36  	ref           int32
    37  	reads         int32
    38  	writes        int32
    39  	onFinalize    unsafe.Pointer
    40  	finalizeState refCountFinalizeState
    41  }
    42  
    43  type refCountFinalizeState struct {
    44  	sync.Mutex
    45  	called   bool
    46  	delayRef int32
    47  }
    48  
    49  // IncRef increments the reference count to this entity.
    50  func (c *RefCount) IncRef() {
    51  	n := atomic.AddInt32(&c.ref, 1)
    52  	tracebackEvent(c, int(n), incRefEvent)
    53  }
    54  
    55  // DecRef decrements the reference count to this entity.
    56  func (c *RefCount) DecRef() {
    57  	n := atomic.AddInt32(&c.ref, -1)
    58  	tracebackEvent(c, int(n), decRefEvent)
    59  
    60  	if n < 0 {
    61  		err := fmt.Errorf("negative ref count, ref=%d", n)
    62  		panicRef(c, err)
    63  	}
    64  }
    65  
    66  // MoveRef signals a move of the ref to this entity.
    67  func (c *RefCount) MoveRef() {
    68  	tracebackEvent(c, c.NumRef(), moveRefEvent)
    69  }
    70  
    71  // NumRef returns the reference count to this entity.
    72  func (c *RefCount) NumRef() int {
    73  	return int(atomic.LoadInt32(&c.ref))
    74  }
    75  
    76  // Finalize will call the finalizer if any, ref count must be zero.
    77  func (c *RefCount) Finalize() {
    78  	n := c.NumRef()
    79  	tracebackEvent(c, n, finalizeEvent)
    80  
    81  	if n != 0 {
    82  		err := fmt.Errorf("finalize before zero ref count, ref=%d", n)
    83  		panicRef(c, err)
    84  	}
    85  
    86  	c.finalizeState.Lock()
    87  	c.finalizeState.called = true
    88  	if c.finalizeState.delayRef == 0 {
    89  		c.finalizeWithLock()
    90  	}
    91  	c.finalizeState.Unlock()
    92  }
    93  
    94  func (c *RefCount) finalizeWithLock() {
    95  	// Reset the finalize called state for reuse.
    96  	c.finalizeState.called = false
    97  	if f := c.OnFinalize(); f != nil {
    98  		f.OnFinalize()
    99  	}
   100  }
   101  
   102  // DelayFinalizer will delay calling the finalizer on this entity
   103  // until the closer returned by the method is called at least once.
   104  // This is useful for dependent resources requiring the lifetime of this
   105  // entityt to be extended.
   106  func (c *RefCount) DelayFinalizer() xresource.SimpleCloser {
   107  	c.finalizeState.Lock()
   108  	c.finalizeState.delayRef++
   109  	c.finalizeState.Unlock()
   110  	return c
   111  }
   112  
   113  // Close implements xresource.SimpleCloser for the purpose of use with DelayFinalizer.
   114  func (c *RefCount) Close() {
   115  	c.finalizeState.Lock()
   116  	c.finalizeState.delayRef--
   117  	if c.finalizeState.called && c.finalizeState.delayRef == 0 {
   118  		c.finalizeWithLock()
   119  	}
   120  	c.finalizeState.Unlock()
   121  }
   122  
   123  // OnFinalize returns the finalizer callback if any or nil otherwise.
   124  func (c *RefCount) OnFinalize() OnFinalize {
   125  	finalizerPtr := (*OnFinalize)(atomic.LoadPointer(&c.onFinalize))
   126  	if finalizerPtr == nil {
   127  		return nil
   128  	}
   129  	return *finalizerPtr
   130  }
   131  
   132  // SetOnFinalize sets the finalizer callback.
   133  func (c *RefCount) SetOnFinalize(f OnFinalize) {
   134  	atomic.StorePointer(&c.onFinalize, unsafe.Pointer(&f))
   135  }
   136  
   137  // IncReads increments the reads count to this entity.
   138  func (c *RefCount) IncReads() {
   139  	tracebackEvent(c, c.NumRef(), incReadsEvent)
   140  	n := atomic.AddInt32(&c.reads, 1)
   141  
   142  	if ref := c.NumRef(); n > 0 && ref < 1 {
   143  		err := fmt.Errorf("read after free: reads=%d, ref=%d", n, ref)
   144  		panicRef(c, err)
   145  	}
   146  }
   147  
   148  // DecReads decrements the reads count to this entity.
   149  func (c *RefCount) DecReads() {
   150  	tracebackEvent(c, c.NumRef(), decReadsEvent)
   151  	n := atomic.AddInt32(&c.reads, -1)
   152  
   153  	if ref := c.NumRef(); ref < 1 {
   154  		err := fmt.Errorf("read finish after free: reads=%d, ref=%d", n, ref)
   155  		panicRef(c, err)
   156  	}
   157  }
   158  
   159  // NumReaders returns the active reads count to this entity.
   160  func (c *RefCount) NumReaders() int {
   161  	return int(atomic.LoadInt32(&c.reads))
   162  }
   163  
   164  // IncWrites increments the writes count to this entity.
   165  func (c *RefCount) IncWrites() {
   166  	tracebackEvent(c, c.NumRef(), incWritesEvent)
   167  	n := atomic.AddInt32(&c.writes, 1)
   168  	ref := c.NumRef()
   169  
   170  	if n > 0 && ref < 1 {
   171  		err := fmt.Errorf("write after free: writes=%d, ref=%d", n, ref)
   172  		panicRef(c, err)
   173  	}
   174  
   175  	if n > 1 {
   176  		err := fmt.Errorf("double write: writes=%d, ref=%d", n, ref)
   177  		panicRef(c, err)
   178  	}
   179  }
   180  
   181  // DecWrites decrements the writes count to this entity.
   182  func (c *RefCount) DecWrites() {
   183  	tracebackEvent(c, c.NumRef(), decWritesEvent)
   184  	n := atomic.AddInt32(&c.writes, -1)
   185  
   186  	if ref := c.NumRef(); ref < 1 {
   187  		err := fmt.Errorf("write finish after free: writes=%d, ref=%d", n, ref)
   188  		panicRef(c, err)
   189  	}
   190  }
   191  
   192  // NumWriters returns the active writes count to this entity.
   193  func (c *RefCount) NumWriters() int {
   194  	return int(atomic.LoadInt32(&c.writes))
   195  }
   196  
   197  // TrackObject sets up the initial internal state of the Ref for
   198  // leak detection.
   199  func (c *RefCount) TrackObject(v interface{}) {
   200  	if !leakDetectionFlag {
   201  		return
   202  	}
   203  
   204  	var size int
   205  
   206  	switch v := reflect.ValueOf(v); v.Kind() {
   207  	case reflect.Ptr:
   208  		size = int(v.Type().Elem().Size())
   209  	case reflect.Array, reflect.Slice, reflect.Chan:
   210  		size = int(v.Type().Elem().Size()) * v.Cap()
   211  	case reflect.String:
   212  		size = v.Len()
   213  	default:
   214  		size = int(v.Type().Size())
   215  	}
   216  
   217  	runtime.SetFinalizer(c, func(c *RefCount) {
   218  		if c.NumRef() == 0 {
   219  			return
   220  		}
   221  
   222  		origin := getDebuggerRef(c).String()
   223  
   224  		leaks.Lock()
   225  		// Keep track of bytes leaked, not objects.
   226  		leaks.m[origin] += uint64(size)
   227  		leaks.Unlock()
   228  	})
   229  }