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  }