github.com/ava-labs/avalanchego@v1.11.11/network/throttling/inbound_resource_throttler_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package throttling 5 6 import ( 7 "context" 8 "testing" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/stretchr/testify/require" 13 "go.uber.org/mock/gomock" 14 15 "github.com/ava-labs/avalanchego/ids" 16 "github.com/ava-labs/avalanchego/snow/networking/tracker" 17 "github.com/ava-labs/avalanchego/snow/networking/tracker/trackermock" 18 "github.com/ava-labs/avalanchego/utils/math/meter" 19 "github.com/ava-labs/avalanchego/utils/resource" 20 "github.com/ava-labs/avalanchego/utils/timer/mockable" 21 ) 22 23 func TestNewSystemThrottler(t *testing.T) { 24 ctrl := gomock.NewController(t) 25 require := require.New(t) 26 reg := prometheus.NewRegistry() 27 clock := mockable.Clock{} 28 clock.Set(time.Now()) 29 resourceTracker, err := tracker.NewResourceTracker(reg, resource.NoUsage, meter.ContinuousFactory{}, time.Second) 30 require.NoError(err) 31 cpuTracker := resourceTracker.CPUTracker() 32 33 config := SystemThrottlerConfig{ 34 Clock: clock, 35 MaxRecheckDelay: time.Second, 36 } 37 targeter := trackermock.NewTargeter(ctrl) 38 throttlerIntf, err := NewSystemThrottler("", reg, config, cpuTracker, targeter) 39 require.NoError(err) 40 require.IsType(&systemThrottler{}, throttlerIntf) 41 throttler := throttlerIntf.(*systemThrottler) 42 require.Equal(clock, config.Clock) 43 require.Equal(time.Second, config.MaxRecheckDelay) 44 require.Equal(cpuTracker, throttler.tracker) 45 require.Equal(targeter, throttler.targeter) 46 } 47 48 func TestSystemThrottler(t *testing.T) { 49 ctrl := gomock.NewController(t) 50 require := require.New(t) 51 52 // Setup 53 mockTracker := trackermock.NewTracker(ctrl) 54 maxRecheckDelay := 100 * time.Millisecond 55 config := SystemThrottlerConfig{ 56 MaxRecheckDelay: maxRecheckDelay, 57 } 58 vdrID, nonVdrID := ids.GenerateTestNodeID(), ids.GenerateTestNodeID() 59 targeter := trackermock.NewTargeter(ctrl) 60 throttler, err := NewSystemThrottler("", prometheus.NewRegistry(), config, mockTracker, targeter) 61 require.NoError(err) 62 63 // Case: Actual usage <= target usage; should return immediately 64 // for both validator and non-validator 65 targeter.EXPECT().TargetUsage(vdrID).Return(1.0).Times(1) 66 mockTracker.EXPECT().Usage(vdrID, gomock.Any()).Return(0.9).Times(1) 67 68 throttler.Acquire(context.Background(), vdrID) 69 70 targeter.EXPECT().TargetUsage(nonVdrID).Return(1.0).Times(1) 71 mockTracker.EXPECT().Usage(nonVdrID, gomock.Any()).Return(0.9).Times(1) 72 73 throttler.Acquire(context.Background(), nonVdrID) 74 75 // Case: Actual usage > target usage; we should wait. 76 // In the first loop iteration inside acquire, 77 // say the actual usage exceeds the target. 78 targeter.EXPECT().TargetUsage(vdrID).Return(0.0).Times(1) 79 mockTracker.EXPECT().Usage(vdrID, gomock.Any()).Return(1.0).Times(1) 80 // Note we'll only actually wait [maxRecheckDelay]. We set [timeUntilAtDiskTarget] 81 // much larger to assert that the min recheck frequency is honored below. 82 timeUntilAtDiskTarget := 100 * maxRecheckDelay 83 mockTracker.EXPECT().TimeUntilUsage(vdrID, gomock.Any(), gomock.Any()).Return(timeUntilAtDiskTarget).Times(1) 84 85 // The second iteration, say the usage is OK. 86 targeter.EXPECT().TargetUsage(vdrID).Return(1.0).Times(1) 87 mockTracker.EXPECT().Usage(vdrID, gomock.Any()).Return(0.0).Times(1) 88 89 onAcquire := make(chan struct{}) 90 91 // Check for validator 92 go func() { 93 throttler.Acquire(context.Background(), vdrID) 94 onAcquire <- struct{}{} 95 }() 96 // Make sure the min re-check frequency is honored 97 select { 98 // Use 5*maxRecheckDelay and not just maxRecheckDelay to give a buffer 99 // and avoid flakiness. If the min re-check freq isn't honored, 100 // we'll wait [timeUntilAtDiskTarget]. 101 case <-time.After(5 * maxRecheckDelay): 102 require.FailNow("should have returned after about [maxRecheckDelay]") 103 case <-onAcquire: 104 } 105 106 targeter.EXPECT().TargetUsage(nonVdrID).Return(0.0).Times(1) 107 mockTracker.EXPECT().Usage(nonVdrID, gomock.Any()).Return(1.0).Times(1) 108 109 mockTracker.EXPECT().TimeUntilUsage(nonVdrID, gomock.Any(), gomock.Any()).Return(timeUntilAtDiskTarget).Times(1) 110 111 targeter.EXPECT().TargetUsage(nonVdrID).Return(1.0).Times(1) 112 mockTracker.EXPECT().Usage(nonVdrID, gomock.Any()).Return(0.0).Times(1) 113 114 // Check for non-validator 115 go func() { 116 throttler.Acquire(context.Background(), nonVdrID) 117 onAcquire <- struct{}{} 118 }() 119 // Make sure the min re-check frequency is honored 120 select { 121 // Use 5*maxRecheckDelay and not just maxRecheckDelay to give a buffer 122 // and avoid flakiness. If the min re-check freq isn't honored, 123 // we'll wait [timeUntilAtDiskTarget]. 124 case <-time.After(5 * maxRecheckDelay): 125 require.FailNow("should have returned after about [maxRecheckDelay]") 126 case <-onAcquire: 127 } 128 } 129 130 func TestSystemThrottlerContextCancel(t *testing.T) { 131 require := require.New(t) 132 ctrl := gomock.NewController(t) 133 134 // Setup 135 mockTracker := trackermock.NewTracker(ctrl) 136 maxRecheckDelay := 10 * time.Second 137 config := SystemThrottlerConfig{ 138 MaxRecheckDelay: maxRecheckDelay, 139 } 140 vdrID := ids.GenerateTestNodeID() 141 targeter := trackermock.NewTargeter(ctrl) 142 throttler, err := NewSystemThrottler("", prometheus.NewRegistry(), config, mockTracker, targeter) 143 require.NoError(err) 144 145 // Case: Actual usage > target usage; we should wait. 146 // Mock the tracker so that the first loop iteration inside acquire, 147 // it says the actual usage exceeds the target. 148 // There should be no second iteration because we've already returned. 149 targeter.EXPECT().TargetUsage(vdrID).Return(0.0).Times(1) 150 mockTracker.EXPECT().Usage(vdrID, gomock.Any()).Return(1.0).Times(1) 151 mockTracker.EXPECT().TimeUntilUsage(vdrID, gomock.Any(), gomock.Any()).Return(maxRecheckDelay).Times(1) 152 onAcquire := make(chan struct{}) 153 // Pass a canceled context into Acquire so that it returns immediately. 154 ctx, cancel := context.WithCancel(context.Background()) 155 cancel() 156 go func() { 157 throttler.Acquire(ctx, vdrID) 158 onAcquire <- struct{}{} 159 }() 160 select { 161 case <-onAcquire: 162 case <-time.After(maxRecheckDelay / 2): 163 // Make sure Acquire returns well before the second check (i.e. "immediately") 164 require.Fail("should have returned immediately") 165 } 166 }