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 }