blob: ed0ced1be152fb4576fe67e453ef48cead380175 [file] [log] [blame]
khenaidoo26721882021-08-11 17:42:52 -04001/*
Joey Armstrong7f8436c2023-07-09 20:23:27 -04002* Copyright 2021-2023 Open Networking Foundation (ONF) and the ONF Contributors
khenaidoo26721882021-08-11 17:42:52 -04003
Joey Armstrong7f8436c2023-07-09 20:23:27 -04004* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
khenaidoo26721882021-08-11 17:42:52 -04007
Joey Armstrong7f8436c2023-07-09 20:23:27 -04008* http://www.apache.org/licenses/LICENSE-2.0
khenaidoo26721882021-08-11 17:42:52 -04009
Joey Armstrong7f8436c2023-07-09 20:23:27 -040010* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
khenaidoo26721882021-08-11 17:42:52 -040015 */
16package grpc
17
18import (
19 "context"
20 "errors"
21 "fmt"
22 "math"
23 "os"
24 "reflect"
25 "sync"
26 "time"
27)
28
29const (
30 incrementalFactor = 1.5
31 minBackOff = 10 * time.Millisecond
32)
33
34type Backoff struct {
35 attempt int
36 initialInterval time.Duration
37 maxElapsedTime time.Duration
38 maxInterval time.Duration
39 totalElapsedTime time.Duration
40 mutex sync.RWMutex
41}
42
43func NewBackoff(initialInterval, maxInterval, maxElapsedTime time.Duration) *Backoff {
44 bo := &Backoff{}
45 bo.initialInterval = initialInterval
46 bo.maxInterval = maxInterval
47 bo.maxElapsedTime = maxElapsedTime
48 return bo
49}
50
51func (bo *Backoff) Backoff(ctx context.Context) error {
52 duration, err := bo.getBackOffDuration()
53 if err != nil {
54 return err
55 }
56
57 ticker := time.NewTicker(duration)
58 defer ticker.Stop()
59 select {
60 case <-ctx.Done():
61 return ctx.Err()
62 case <-ticker.C:
63 }
64 return nil
65}
66
67func (bo *Backoff) getBackOffDuration() (duration time.Duration, err error) {
68 err = nil
69 defer func() {
70 bo.mutex.Lock()
71 defer bo.mutex.Unlock()
72 bo.attempt += 1
73 bo.totalElapsedTime += duration
74 if bo.maxElapsedTime > 0 && bo.totalElapsedTime > bo.maxElapsedTime {
75 err = errors.New("max elapsed backoff time reached")
76 }
77 }()
78
79 if bo.initialInterval <= minBackOff {
80 bo.initialInterval = minBackOff
81 }
82 if bo.initialInterval > bo.maxInterval {
83 duration = bo.initialInterval
84 return
85 }
86
87 // Calculate incremental duration
88 minf := float64(bo.initialInterval)
89 durf := minf * math.Pow(incrementalFactor, float64(bo.attempt))
90
91 if durf > math.MaxInt64 {
92 duration = bo.maxInterval
93 return
94 }
95 duration = time.Duration(durf)
96
97 //Keep within bounds
98 if duration < bo.initialInterval {
99 duration = bo.initialInterval
100 }
101 if duration > bo.maxInterval {
102 duration = bo.maxInterval
103 }
104 return
105}
106
107func (bo *Backoff) Reset() {
108 bo.mutex.Lock()
109 defer bo.mutex.Unlock()
110 bo.attempt = 0
111 bo.totalElapsedTime = 0
112}
113
114func SetFromEnvVariable(key string, variableToSet interface{}) error {
115 if _, ok := variableToSet.(*time.Duration); !ok {
116 return fmt.Errorf("unsupported type %T", variableToSet)
117 }
118 if valStr, present := os.LookupEnv(key); present {
119 val, err := time.ParseDuration(valStr)
120 if err != nil {
121 return err
122 }
123 reflect.ValueOf(variableToSet).Elem().Set(reflect.ValueOf(val))
124 }
125 return nil
126}