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  }