github.com/kaydxh/golang@v0.0.131/go/time/wait.go (about) 1 /* 2 *Copyright (c) 2022, kaydxh 3 * 4 *Permission is hereby granted, free of charge, to any person obtaining a copy 5 *of this software and associated documentation files (the "Software"), to deal 6 *in the Software without restriction, including without limitation the rights 7 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 *copies of the Software, and to permit persons to whom the Software is 9 *furnished to do so, subject to the following conditions: 10 * 11 *The above copyright notice and this permission notice shall be included in all 12 *copies or substantial portions of the Software. 13 * 14 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 *SOFTWARE. 21 */ 22 package time 23 24 import ( 25 "context" 26 "errors" 27 "fmt" 28 "time" 29 30 errors_ "github.com/kaydxh/golang/go/errors" 31 runtime_ "github.com/kaydxh/golang/go/runtime" 32 "github.com/sirupsen/logrus" 33 ) 34 35 var ErrTimeout = errors.New("timeout error") 36 37 // Until loops until context timout, running f every period. 38 // Until is syntactic sugar on top of JitterUntil with zero jitter factor and 39 // with sliding = true (which means the timer for period starts after the f 40 // completes). 41 func UntilWithContxt( 42 ctx context.Context, 43 f func(ctx context.Context) error, period time.Duration) { 44 JitterUntilWithContext(ctx, f, period) 45 } 46 47 func JitterUntilWithContext( 48 ctx context.Context, 49 f func(ctx context.Context) error, 50 period time.Duration, 51 ) { 52 BackOffUntilWithContext(ctx, f, 53 NewExponentialBackOff( 54 // forever run 55 WithExponentialBackOffOptionMaxElapsedTime(0), 56 WithExponentialBackOffOptionInitialInterval(period), 57 // ensure equal interval 58 WithExponentialBackOffOptionMultiplier(1), 59 WithExponentialBackOffOptionRandomizationFactor(0), 60 ), true, true) 61 62 } 63 64 // RetryWithContext retryTime is not include the first call 65 func RetryWithContext( 66 ctx context.Context, 67 f func(ctx context.Context) error, 68 period time.Duration, 69 retryTimes int, 70 ) error { 71 return BackOffUntilWithContext(ctx, f, 72 NewExponentialBackOff( 73 // forever run 74 WithExponentialBackOffOptionMaxElapsedTime(0), 75 WithExponentialBackOffOptionInitialInterval(period), 76 // ensure equal interval 77 WithExponentialBackOffOptionMultiplier(1), 78 WithExponentialBackOffOptionRandomizationFactor(0), 79 WithExponentialBackOffOptionMaxElapsedCount(retryTimes), 80 ), true, false) 81 } 82 83 // loop true -> BackOffUntilWithContext return until time expired 84 // loop false -> BackOffUntilWithContext return if f return nil, or time expired 85 func BackOffUntilWithContext( 86 ctx context.Context, 87 f func(ctx context.Context) error, 88 backoff Backoff, 89 sliding bool, 90 loop bool, 91 ) (err error) { 92 var ( 93 t time.Duration 94 remain time.Duration 95 expired bool 96 ) 97 98 for { 99 select { 100 case <-ctx.Done(): 101 return fmt.Errorf("context cancelled: %v", ctx.Err()) 102 default: 103 } 104 105 tc := New(true) 106 if !sliding { 107 // If it is false then period includes the runtime for f 108 t, expired = backoff.NextBackOff() 109 } 110 111 func() { 112 defer runtime_.Recover() 113 err = f(ctx) 114 logrus.Infof("finish call function, err[%v]", err) 115 }() 116 117 if !loop { 118 if err == nil { 119 return nil 120 } 121 } 122 123 if sliding { 124 // If sliding is true, the period is computed after f runs 125 tc.Reset() 126 t, expired = backoff.NextBackOff() 127 } 128 if !expired { 129 return errors_.Errore( 130 fmt.Errorf("got max wait time or max count"), 131 err) 132 } 133 134 remain = t - tc.Elapse() 135 // fmt.Printf("remain: %v, data: %v\n", remain, time.Now().String()) 136 137 func() { 138 if remain <= 0 { 139 return 140 } 141 timer := time.NewTimer(remain) 142 defer timer.Stop() 143 144 // NOTE: b/c there is no priority selection in golang 145 // it is possible for this to race, meaning we could 146 // trigger t.C and stopCh, and t.C select falls through. 147 // In order to mitigate we re-check stopCh at the beginning 148 // of every loop to prevent extra executions of f(). 149 150 select { 151 case <-ctx.Done(): 152 return 153 case <-timer.C: 154 } 155 }() 156 } 157 } 158 159 func CallWithTimeout(ctx context.Context, timeout time.Duration, f func(ctx context.Context) error) error { 160 161 tc := New(true) 162 // nerver timeout 163 if timeout <= 0 { 164 err := f(ctx) 165 tc.Tick("call func") 166 logrus.WithField("modulel", "CallWithTimeout").WithField("timeout", timeout).Infof("finish call function %v, err: %v", tc.String(), err) 167 return err 168 } 169 170 var errs []error 171 done := make(chan struct{}, 1) 172 timer := time.NewTimer(timeout) 173 defer timer.Stop() 174 175 go func() { 176 err := f(ctx) 177 if err != nil { 178 errs = append(errs, err) 179 } 180 tc.Tick("call func") 181 182 done <- struct{}{} 183 logrus.WithField("modulel", "CallWithTimeout").WithField("timeout", timeout).Infof("finish call function %v, err: %v", tc.String(), err) 184 }() 185 186 select { 187 case <-ctx.Done(): 188 return ctx.Err() 189 case <-done: 190 case <-timer.C: 191 return ErrTimeout 192 } 193 194 return errors_.NewAggregate(errs) 195 196 }