github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/timeoutcollector/timeout_cache_test.go (about) 1 package timeoutcollector 2 3 import ( 4 "fmt" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/require" 10 11 "github.com/onflow/flow-go/consensus/hotstuff/helper" 12 "github.com/onflow/flow-go/consensus/hotstuff/model" 13 "github.com/onflow/flow-go/utils/unittest" 14 ) 15 16 // TestTimeoutObjectsCache_View tests that View returns same value that was set by constructor 17 func TestTimeoutObjectsCache_View(t *testing.T) { 18 view := uint64(100) 19 cache := NewTimeoutObjectsCache(view) 20 require.Equal(t, view, cache.View()) 21 } 22 23 // TestTimeoutObjectsCache_AddTimeoutObjectRepeatedTimeout tests that AddTimeoutObject skips duplicated timeouts 24 func TestTimeoutObjectsCache_AddTimeoutObjectRepeatedTimeout(t *testing.T) { 25 t.Parallel() 26 27 view := uint64(100) 28 cache := NewTimeoutObjectsCache(view) 29 timeout := helper.TimeoutObjectFixture(helper.WithTimeoutObjectView(view)) 30 31 require.NoError(t, cache.AddTimeoutObject(timeout)) 32 err := cache.AddTimeoutObject(timeout) 33 require.ErrorIs(t, err, ErrRepeatedTimeout) 34 require.Len(t, cache.All(), 1) 35 } 36 37 // TestTimeoutObjectsCache_AddTimeoutObjectIncompatibleView tests that adding timeout with incompatible view results in error 38 func TestTimeoutObjectsCache_AddTimeoutObjectIncompatibleView(t *testing.T) { 39 t.Parallel() 40 41 view := uint64(100) 42 cache := NewTimeoutObjectsCache(view) 43 timeout := helper.TimeoutObjectFixture(helper.WithTimeoutObjectView(view + 1)) 44 err := cache.AddTimeoutObject(timeout) 45 require.ErrorIs(t, err, ErrTimeoutForIncompatibleView) 46 } 47 48 // TestTimeoutObjectsCache_GetTimeout tests that GetTimeout method returns the first added timeout 49 // for a given signer, if any timeout has been added. 50 func TestTimeoutObjectsCache_GetTimeout(t *testing.T) { 51 view := uint64(100) 52 knownTimeout := helper.TimeoutObjectFixture(helper.WithTimeoutObjectView(view)) 53 doubleTimeout := helper.TimeoutObjectFixture( 54 helper.WithTimeoutObjectView(view), 55 helper.WithTimeoutObjectSignerID(knownTimeout.SignerID)) 56 57 cache := NewTimeoutObjectsCache(view) 58 59 // unknown timeout 60 timeout, found := cache.GetTimeoutObject(unittest.IdentifierFixture()) 61 require.Nil(t, timeout) 62 require.False(t, found) 63 64 // known timeout 65 err := cache.AddTimeoutObject(knownTimeout) 66 require.NoError(t, err) 67 timeout, found = cache.GetTimeoutObject(knownTimeout.SignerID) 68 require.Equal(t, knownTimeout, timeout) 69 require.True(t, found) 70 71 // for a signer ID with a known timeout, the cache should memorize the _first_ encountered timeout 72 err = cache.AddTimeoutObject(doubleTimeout) 73 require.True(t, model.IsDoubleTimeoutError(err)) 74 timeout, found = cache.GetTimeoutObject(doubleTimeout.SignerID) 75 require.Equal(t, knownTimeout, timeout) 76 require.True(t, found) 77 } 78 79 // TestTimeoutObjectsCache_All tests that All returns previously added timeouts. 80 func TestTimeoutObjectsCache_All(t *testing.T) { 81 t.Parallel() 82 83 view := uint64(100) 84 cache := NewTimeoutObjectsCache(view) 85 expectedTimeouts := make([]*model.TimeoutObject, 5) 86 for i := range expectedTimeouts { 87 timeout := helper.TimeoutObjectFixture(helper.WithTimeoutObjectView(view)) 88 expectedTimeouts[i] = timeout 89 require.NoError(t, cache.AddTimeoutObject(timeout)) 90 } 91 require.ElementsMatch(t, expectedTimeouts, cache.All()) 92 } 93 94 // BenchmarkAdd measured the time it takes to add `numberTimeouts` concurrently to the TimeoutObjectsCache. 95 // On MacBook with Intel i7-7820HQ CPU @ 2.90GHz: 96 // adding 1 million timeouts in total, with 20 threads concurrently, took 0.48s 97 func BenchmarkAdd(b *testing.B) { 98 numberTimeouts := 1_000_000 99 threads := 20 100 101 // Setup: create worker routines and timeouts to feed 102 view := uint64(10) 103 cache := NewTimeoutObjectsCache(view) 104 105 var start sync.WaitGroup 106 start.Add(threads) 107 var done sync.WaitGroup 108 done.Add(threads) 109 110 n := numberTimeouts / threads 111 112 for ; threads > 0; threads-- { 113 go func(i int) { 114 // create timeouts and signal ready 115 timeouts := make([]model.TimeoutObject, 0, n) 116 for len(timeouts) < n { 117 t := helper.TimeoutObjectFixture(helper.WithTimeoutObjectView(view)) 118 timeouts = append(timeouts, *t) 119 } 120 start.Done() 121 122 // Wait for last worker routine to signal ready. Then, 123 // feed all timeouts into cache 124 start.Wait() 125 for _, v := range timeouts { 126 err := cache.AddTimeoutObject(&v) 127 require.NoError(b, err) 128 } 129 done.Done() 130 }(threads) 131 } 132 start.Wait() 133 t1 := time.Now() 134 done.Wait() 135 duration := time.Since(t1) 136 fmt.Printf("=> adding %d timeouts to Cache took %f seconds\n", cache.Size(), duration.Seconds()) 137 }