github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/checked/ref_test.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  	"os"
    26  	"reflect"
    27  	"runtime"
    28  	"sync"
    29  	"sync/atomic"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/leanovate/gopter"
    34  	"github.com/leanovate/gopter/gen"
    35  	"github.com/leanovate/gopter/prop"
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  
    39  	xresource "github.com/m3db/m3/src/x/resource"
    40  )
    41  
    42  func TestRefCountNegativeRefCount(t *testing.T) {
    43  	elem := &RefCount{}
    44  
    45  	var err error
    46  	SetPanicFn(func(e error) {
    47  		err = e
    48  	})
    49  	defer ResetPanicFn()
    50  
    51  	elem.IncRef()
    52  	assert.Equal(t, 1, elem.NumRef())
    53  	assert.Nil(t, err)
    54  
    55  	elem.DecRef()
    56  	assert.Equal(t, 0, elem.NumRef())
    57  	assert.Nil(t, err)
    58  
    59  	elem.DecRef()
    60  	assert.Equal(t, -1, elem.NumRef())
    61  	assert.Error(t, err)
    62  	assert.Equal(t, "negative ref count, ref=-1", err.Error())
    63  }
    64  
    65  func TestRefCountFinalizeBeforeZeroRef(t *testing.T) {
    66  	elem := &RefCount{}
    67  
    68  	var err error
    69  	SetPanicFn(func(e error) {
    70  		err = e
    71  	})
    72  	defer ResetPanicFn()
    73  
    74  	elem.IncRef()
    75  	elem.IncRef()
    76  	assert.Nil(t, err)
    77  
    78  	elem.Finalize()
    79  	assert.Error(t, err)
    80  	assert.Equal(t, "finalize before zero ref count, ref=2", err.Error())
    81  }
    82  
    83  func TestRefCountFinalizeCallsFinalizer(t *testing.T) {
    84  	elem := &RefCount{}
    85  
    86  	onFinalizeCalls := 0
    87  	onFinalize := OnFinalize(OnFinalizeFn(func() {
    88  		onFinalizeCalls++
    89  	}))
    90  	elem.SetOnFinalize(onFinalize)
    91  	assert.Equal(t,
    92  		reflect.ValueOf(onFinalize).Pointer(),
    93  		reflect.ValueOf(elem.OnFinalize()).Pointer())
    94  
    95  	var err error
    96  	SetPanicFn(func(e error) {
    97  		err = e
    98  	})
    99  	defer ResetPanicFn()
   100  
   101  	elem.IncRef()
   102  	elem.DecRef()
   103  	elem.Finalize()
   104  	assert.Nil(t, err)
   105  
   106  	assert.Equal(t, 1, onFinalizeCalls)
   107  }
   108  
   109  func TestRefCountFinalizerNil(t *testing.T) {
   110  	elem := &RefCount{}
   111  
   112  	assert.Equal(t, (OnFinalize)(nil), elem.OnFinalize())
   113  
   114  	finalizerCalls := 0
   115  	elem.SetOnFinalize(OnFinalize(OnFinalizeFn(func() {
   116  		finalizerCalls++
   117  	})))
   118  
   119  	assert.NotNil(t, elem.OnFinalize())
   120  
   121  	elem.Finalize()
   122  
   123  	assert.Equal(t, 1, finalizerCalls)
   124  }
   125  
   126  func TestRefCountReadAfterFree(t *testing.T) {
   127  	elem := &RefCount{}
   128  
   129  	var err error
   130  	SetPanicFn(func(e error) {
   131  		err = e
   132  	})
   133  	defer ResetPanicFn()
   134  
   135  	elem.IncRef()
   136  	elem.DecRef()
   137  	assert.Nil(t, err)
   138  
   139  	elem.IncReads()
   140  	assert.Error(t, err)
   141  	assert.Equal(t, "read after free: reads=1, ref=0", err.Error())
   142  }
   143  
   144  func TestRefCountReadFinishAfterFree(t *testing.T) {
   145  	elem := &RefCount{}
   146  
   147  	var err error
   148  	SetPanicFn(func(e error) {
   149  		err = e
   150  	})
   151  	defer ResetPanicFn()
   152  
   153  	elem.IncRef()
   154  	elem.IncReads()
   155  	assert.Equal(t, 1, elem.NumReaders())
   156  	elem.DecRef()
   157  	assert.Nil(t, err)
   158  
   159  	elem.DecReads()
   160  	assert.Error(t, err)
   161  	assert.Equal(t, "read finish after free: reads=0, ref=0", err.Error())
   162  }
   163  
   164  func TestRefCountWriteAfterFree(t *testing.T) {
   165  	elem := &RefCount{}
   166  
   167  	var err error
   168  	SetPanicFn(func(e error) {
   169  		err = e
   170  	})
   171  	defer ResetPanicFn()
   172  
   173  	elem.IncRef()
   174  	elem.DecRef()
   175  	assert.Nil(t, err)
   176  
   177  	elem.IncWrites()
   178  	assert.Error(t, err)
   179  	assert.Equal(t, "write after free: writes=1, ref=0", err.Error())
   180  }
   181  
   182  func TestRefCountDoubleWrite(t *testing.T) {
   183  	elem := &RefCount{}
   184  
   185  	var err error
   186  	SetPanicFn(func(e error) {
   187  		err = e
   188  	})
   189  	defer ResetPanicFn()
   190  
   191  	elem.IncRef()
   192  	elem.IncWrites()
   193  	assert.Equal(t, 1, elem.NumWriters())
   194  
   195  	elem.IncWrites()
   196  	assert.Error(t, err)
   197  	assert.Equal(t, "double write: writes=2, ref=1", err.Error())
   198  }
   199  
   200  func TestRefCountWriteFinishAfterFree(t *testing.T) {
   201  	elem := &RefCount{}
   202  
   203  	var err error
   204  	SetPanicFn(func(e error) {
   205  		err = e
   206  	})
   207  	defer ResetPanicFn()
   208  
   209  	elem.IncRef()
   210  	elem.IncWrites()
   211  	assert.Equal(t, 1, elem.NumWriters())
   212  	elem.DecRef()
   213  	assert.Nil(t, err)
   214  
   215  	elem.DecWrites()
   216  	assert.Error(t, err)
   217  	assert.Equal(t, "write finish after free: writes=0, ref=0", err.Error())
   218  }
   219  
   220  func TestRefCountDelayFinalizer(t *testing.T) {
   221  	// NB(r): Make sure to reuse elem so that reuse is accounted for.
   222  	elem := &RefCount{}
   223  
   224  	tests := []struct {
   225  		numDelay int
   226  	}{
   227  		{
   228  			numDelay: 1,
   229  		},
   230  		{
   231  			numDelay: 2,
   232  		},
   233  		{
   234  			numDelay: 1024,
   235  		},
   236  		{
   237  			numDelay: 4096,
   238  		},
   239  	}
   240  
   241  	for _, test := range tests {
   242  		t.Run(fmt.Sprintf("num_delay=%d", test.numDelay), func(t *testing.T) {
   243  			onFinalizeCalls := int32(0)
   244  			onFinalize := OnFinalize(OnFinalizeFn(func() {
   245  				atomic.AddInt32(&onFinalizeCalls, 1)
   246  			}))
   247  
   248  			elem.SetOnFinalize(onFinalize)
   249  			elem.IncRef()
   250  			elem.DecRef()
   251  
   252  			delays := make([]xresource.SimpleCloser, 0, test.numDelay)
   253  			for i := 0; i < test.numDelay; i++ {
   254  				delays = append(delays, elem.DelayFinalizer())
   255  			}
   256  
   257  			elem.Finalize()
   258  			require.Equal(t, int32(0), atomic.LoadInt32(&onFinalizeCalls))
   259  
   260  			var startWaitingWg, startBeginWg, doneWg sync.WaitGroup
   261  			startBeginWg.Add(1)
   262  			for _, delay := range delays {
   263  				delay := delay
   264  				startWaitingWg.Add(1)
   265  				doneWg.Add(1)
   266  				go func() {
   267  					startWaitingWg.Done()
   268  					startBeginWg.Wait()
   269  					delay.Close()
   270  					doneWg.Done()
   271  				}()
   272  			}
   273  
   274  			startWaitingWg.Wait() // Wait for ready to go.
   275  			require.Equal(t, int32(0), atomic.LoadInt32(&onFinalizeCalls))
   276  
   277  			startBeginWg.Done() // Open flood gate.
   278  			doneWg.Wait()       // Wait for all done.
   279  
   280  			require.Equal(t, int32(1), atomic.LoadInt32(&onFinalizeCalls))
   281  		})
   282  	}
   283  }
   284  
   285  func TestRefCountDelayFinalizerDoesNotFinalizeUntilDone(t *testing.T) {
   286  	elem := &RefCount{}
   287  
   288  	onFinalizeCalls := int32(0)
   289  	onFinalize := OnFinalize(OnFinalizeFn(func() {
   290  		atomic.AddInt32(&onFinalizeCalls, 1)
   291  	}))
   292  
   293  	elem.SetOnFinalize(onFinalize)
   294  
   295  	// Delay finalization and complete immediately, should not cause finalization.
   296  	delay := elem.DelayFinalizer()
   297  	delay.Close()
   298  
   299  	require.Equal(t, int32(0), atomic.LoadInt32(&onFinalizeCalls))
   300  
   301  	elem.Finalize()
   302  	require.Equal(t, int32(1), atomic.LoadInt32(&onFinalizeCalls))
   303  }
   304  
   305  func TestRefCountDelayFinalizerPropTest(t *testing.T) {
   306  	var (
   307  		parameters = gopter.DefaultTestParameters()
   308  		seed       = time.Now().UnixNano()
   309  		props      = gopter.NewProperties(parameters)
   310  		reporter   = gopter.NewFormatedReporter(true, 160, os.Stdout)
   311  	)
   312  	parameters.MinSuccessfulTests = 1024
   313  	parameters.Rng.Seed(seed)
   314  
   315  	type testInput struct {
   316  		numEvents                 int
   317  		finalizeBeforeDuringAfter int
   318  		finalizeDuringIndex       int
   319  	}
   320  
   321  	genTestInput := func() gopter.Gen {
   322  		return gen.
   323  			IntRange(1, 64).
   324  			FlatMap(func(input interface{}) gopter.Gen {
   325  				numEvents := input.(int)
   326  				return gopter.CombineGens(
   327  					gen.IntRange(0, 1),
   328  					gen.IntRange(0, numEvents-1),
   329  				).Map(func(input []interface{}) testInput {
   330  					finalizeBeforeDuringAfter := input[0].(int)
   331  					finalizeDuringIndex := input[1].(int)
   332  					return testInput{
   333  						numEvents:                 numEvents,
   334  						finalizeBeforeDuringAfter: finalizeBeforeDuringAfter,
   335  						finalizeDuringIndex:       finalizeDuringIndex,
   336  					}
   337  				})
   338  			}, reflect.TypeOf(testInput{}))
   339  	}
   340  
   341  	// Use single ref count to make sure can be safely reused.
   342  	elem := &RefCount{}
   343  
   344  	props.Property("should finalize always once",
   345  		prop.ForAll(func(input testInput) (bool, error) {
   346  			onFinalizeCalls := int32(0)
   347  			onFinalize := OnFinalize(OnFinalizeFn(func() {
   348  				if n := atomic.AddInt32(&onFinalizeCalls, 1); n > 1 {
   349  					panic(fmt.Sprintf("called finalizer more than once: %v", n))
   350  				}
   351  			}))
   352  			elem.SetOnFinalize(onFinalize)
   353  
   354  			var startWaitingWg, startBeginWg, startDoneWg, continueWg, doneWg sync.WaitGroup
   355  			startBeginWg.Add(1)
   356  			continueWg.Add(1)
   357  			startWaitingWg.Add(input.numEvents)
   358  			startDoneWg.Add(input.numEvents)
   359  			doneWg.Add(input.numEvents)
   360  			for j := 0; j < input.numEvents; j++ {
   361  				j := j // Capture for lambda
   362  				go func() {
   363  					startWaitingWg.Done()
   364  					startBeginWg.Wait()
   365  
   366  					delay := elem.DelayFinalizer()
   367  
   368  					// Wait for delayed finalize calls done, cannot call
   369  					// finalize before all delay finalize calls have been issued.
   370  					startDoneWg.Done()
   371  					continueWg.Wait()
   372  
   373  					if input.finalizeBeforeDuringAfter == 0 && j == input.finalizeDuringIndex {
   374  						elem.Finalize() // Trigger after an element has began delayed close
   375  					}
   376  
   377  					delay.Close()
   378  
   379  					if input.finalizeBeforeDuringAfter == 1 && j == input.finalizeDuringIndex {
   380  						elem.Finalize() // Trigger after an element has finished delayed closed
   381  					}
   382  
   383  					doneWg.Done()
   384  				}()
   385  			}
   386  
   387  			startWaitingWg.Wait() // Wait for ready to go.
   388  			startBeginWg.Done()   // Open flood gate.
   389  			startDoneWg.Wait()    // Wait for delay finalize to be called.
   390  			continueWg.Done()     // Continue the close calls.
   391  			doneWg.Wait()         // Wait for all done.
   392  
   393  			if v := atomic.LoadInt32(&onFinalizeCalls); v != 1 {
   394  				return false, fmt.Errorf(
   395  					"finalizer should have been called once, instead: v=%v", v)
   396  			}
   397  
   398  			return true, nil
   399  		}, genTestInput()))
   400  
   401  	if !props.Run(reporter) {
   402  		t.Errorf("failed with initial seed: %d", seed)
   403  	}
   404  }
   405  
   406  func TestLeakDetection(t *testing.T) {
   407  	EnableLeakDetection()
   408  	defer DisableLeakDetection()
   409  
   410  	{
   411  		v := &RefCount{}
   412  		v.TrackObject(v)
   413  		v.IncRef()
   414  	}
   415  
   416  	runtime.GC()
   417  
   418  	var l []string
   419  
   420  	for ; len(l) == 0; l = DumpLeaks() {
   421  		// Finalizers are run in a separate goroutine, so we have to wait
   422  		// a little bit here.
   423  		time.Sleep(100 * time.Millisecond)
   424  	}
   425  
   426  	assert.NotEmpty(t, l)
   427  }