github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/backoff/backoff.go (about)

     1  // Copyright 2019 PingCAP, 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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package backoff
    15  
    16  import (
    17  	"math"
    18  	"math/rand"
    19  	"time"
    20  
    21  	"github.com/pingcap/tiflow/dm/pkg/terror"
    22  )
    23  
    24  // Backoff is an exponential counter, it starts from `Min` duration, and after
    25  // every call to `Duration` method the duration will be multiplied by `Factor`,
    26  // but it never exceeds `Max`. Backoff is not thread-safe.
    27  type Backoff struct {
    28  	// cwnd is the congestion window
    29  	cwnd int
    30  
    31  	// Factor is the multiplying factor for each increment step
    32  	Factor float64
    33  
    34  	// Jitter eases contention by randomizing backoff steps
    35  	Jitter bool
    36  
    37  	// Min and Max are the minimum and maximum values of the counter
    38  	Min, Max time.Duration
    39  }
    40  
    41  // NewBackoff creates a new backoff instance.
    42  func NewBackoff(factor float64, jitter bool, min, max time.Duration) (*Backoff, error) {
    43  	if factor <= 0 {
    44  		return nil, terror.ErrBackoffArgsNotValid.Generate("factor", factor)
    45  	}
    46  	if min < 0 {
    47  		return nil, terror.ErrBackoffArgsNotValid.Generate("min", min)
    48  	}
    49  	if max < 0 || max < min {
    50  		return nil, terror.ErrBackoffArgsNotValid.Generate("max", max)
    51  	}
    52  	return &Backoff{
    53  		Factor: factor,
    54  		Jitter: jitter,
    55  		Min:    min,
    56  		Max:    max,
    57  	}, nil
    58  }
    59  
    60  // Duration returns the duration for the current cwnd and increases the cwnd counter.
    61  func (b *Backoff) Duration() time.Duration {
    62  	d := b.Current()
    63  	b.Forward()
    64  	return d
    65  }
    66  
    67  // Current returns the duration for the current cwnd, but doesn't increase the
    68  // cwnd counter.
    69  func (b *Backoff) Current() time.Duration {
    70  	return b.durationcwnd(b.cwnd)
    71  }
    72  
    73  // BoundaryForward checks whether `Current` reaches `Max` duration, if not then
    74  // increases the cwnd counter, else does nothing.
    75  func (b *Backoff) BoundaryForward() {
    76  	if b.Current() < b.Max {
    77  		b.cwnd++
    78  	}
    79  }
    80  
    81  // Forward increases the cwnd counter.
    82  func (b *Backoff) Forward() {
    83  	b.cwnd++
    84  }
    85  
    86  // Rollback try to decrease cwnd by one if it is greater or equal than one.
    87  // This is used for we have a long enough duration without backoff try.
    88  func (b *Backoff) Rollback() {
    89  	if b.cwnd > 0 {
    90  		b.cwnd--
    91  	}
    92  }
    93  
    94  // durationcwnd returns the duration for a specific cwnd. The first
    95  // cwnd should be 0.
    96  func (b *Backoff) durationcwnd(cwnd int) time.Duration {
    97  	minf := float64(b.Min)
    98  	durf := minf * math.Pow(b.Factor, float64(cwnd))
    99  	// Full jitter and Decorr jitter is more aggressive and more suitable for fast recovery
   100  	// We use Equal jitter here to achieve more stability
   101  	// refer to: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
   102  	if b.Jitter {
   103  		if durf <= minf {
   104  			return b.Min
   105  		}
   106  		durf = durf/2 + rand.Float64()*(durf/2)
   107  	}
   108  	// why minus 512 here, because i = 512 is the minimal value that ensures
   109  	// float64(math.MaxInt64) > float64(math.MaxInt64-i)) is true
   110  	if durf > float64(math.MaxInt64-512) {
   111  		return b.Max
   112  	}
   113  	dur := time.Duration(durf)
   114  	if dur > b.Max {
   115  		return b.Max
   116  	}
   117  	return dur
   118  }
   119  
   120  // Reset restarts the current cwnd counter to zero.
   121  func (b *Backoff) Reset() {
   122  	b.cwnd = 0
   123  }