github.com/livekit/protocol@v1.39.3/utils/hedge.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 "time" 21 22 "go.uber.org/multierr" 23 ) 24 25 var ErrMaxAttemptsReached = errors.New("max attempts reached") 26 27 type HedgeParams[T any] struct { 28 Timeout time.Duration 29 RetryDelay time.Duration 30 MaxAttempts int 31 IsRecoverable func(err error) bool 32 Func func(context.Context) (T, error) 33 } 34 35 // race retries if the function takes too long to return 36 // |---------------- attempt 1 ----------------| 37 // | delay |--------- attempt 2 ---------| 38 func HedgeCall[T any](ctx context.Context, params HedgeParams[T]) (v T, err error) { 39 ctx, cancel := context.WithTimeout(ctx, params.Timeout) 40 defer cancel() 41 42 type result struct { 43 value T 44 err error 45 } 46 ch := make(chan result, params.MaxAttempts) 47 48 race := func() { 49 value, err := params.Func(ctx) 50 ch <- result{value, err} 51 } 52 53 var attempt, done int 54 delay := time.NewTimer(0) 55 defer delay.Stop() 56 57 for { 58 select { 59 case <-delay.C: 60 go race() 61 if attempt++; attempt < params.MaxAttempts { 62 delay.Reset(params.RetryDelay) 63 } 64 case res := <-ch: 65 if res.err == nil { 66 return res.value, nil 67 } 68 69 err = multierr.Append(err, res.err) 70 if params.IsRecoverable != nil && !params.IsRecoverable(res.err) { 71 return 72 } 73 if done++; done == params.MaxAttempts { 74 err = multierr.Append(err, ErrMaxAttemptsReached) 75 return 76 } 77 case <-ctx.Done(): 78 err = multierr.Append(err, ctx.Err()) 79 return 80 } 81 } 82 }