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