github.com/livekit/protocol@v1.39.3/utils/hedge_test.go (about) 1 // Copyright 2023 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "context" 19 "errors" 20 "testing" 21 "time" 22 23 "github.com/stretchr/testify/require" 24 "go.uber.org/atomic" 25 ) 26 27 func TestHedgeCall(t *testing.T) { 28 t.Run("success", func(t *testing.T) { 29 var attempts atomic.Uint32 30 res, err := HedgeCall(context.Background(), HedgeParams[uint32]{ 31 Timeout: 200 * time.Millisecond, 32 RetryDelay: 50 * time.Millisecond, 33 MaxAttempts: 2, 34 Func: func(context.Context) (uint32, error) { 35 n := attempts.Add(1) 36 time.Sleep(75 * time.Millisecond) 37 return n, nil 38 }, 39 }) 40 require.NoError(t, err) 41 require.EqualValues(t, 1, res) 42 require.EqualValues(t, 2, attempts.Load()) 43 }) 44 45 t.Run("recoverable error", func(t *testing.T) { 46 var recoverableErr = errors.New("recoverable") 47 48 var attempts atomic.Uint32 49 res, err := HedgeCall(context.Background(), HedgeParams[uint32]{ 50 Timeout: 200 * time.Millisecond, 51 RetryDelay: 50 * time.Millisecond, 52 MaxAttempts: 2, 53 IsRecoverable: func(err error) bool { 54 return errors.Is(err, recoverableErr) 55 }, 56 Func: func(context.Context) (uint32, error) { 57 n := attempts.Add(1) 58 if n == 1 { 59 return n, recoverableErr 60 } 61 return n, nil 62 }, 63 }) 64 require.NoError(t, err) 65 require.EqualValues(t, 2, res) 66 }) 67 68 t.Run("unrecoverable error", func(t *testing.T) { 69 var recoverableErr = errors.New("recoverable") 70 var unrecoverableErr = errors.New("unrecoverable") 71 72 var attempts atomic.Uint32 73 _, err := HedgeCall(context.Background(), HedgeParams[uint32]{ 74 Timeout: 200 * time.Millisecond, 75 RetryDelay: 50 * time.Millisecond, 76 MaxAttempts: 3, 77 IsRecoverable: func(err error) bool { 78 return !errors.Is(err, unrecoverableErr) 79 }, 80 Func: func(context.Context) (uint32, error) { 81 n := attempts.Add(1) 82 if n == 1 { 83 return n, recoverableErr 84 } 85 return n, unrecoverableErr 86 }, 87 }) 88 require.ErrorIs(t, err, unrecoverableErr) 89 require.EqualValues(t, 2, attempts.Load()) 90 }) 91 92 t.Run("max failures", func(t *testing.T) { 93 var attempts atomic.Uint32 94 _, err := HedgeCall(context.Background(), HedgeParams[uint32]{ 95 Timeout: 200 * time.Millisecond, 96 RetryDelay: 50 * time.Millisecond, 97 MaxAttempts: 2, 98 Func: func(context.Context) (uint32, error) { 99 n := attempts.Add(1) 100 return n, errors.New("failure") 101 }, 102 }) 103 require.ErrorIs(t, err, ErrMaxAttemptsReached) 104 require.EqualValues(t, 2, attempts.Load()) 105 }) 106 }