diff --git a/pacer/pacer.go b/pacer/pacer.go index d2dd8ae3e..045220aef 100644 --- a/pacer/pacer.go +++ b/pacer/pacer.go @@ -15,6 +15,7 @@ type Pacer struct { minSleep time.Duration // minimum sleep time maxSleep time.Duration // maximum sleep time decayConstant uint // decay constant + attackConstant uint // attack constant pacer chan struct{} // To pace the operations sleepTime time.Duration // Time to sleep for each transaction retries int // Max number of retries @@ -58,11 +59,12 @@ type Paced func() (bool, error) // New returns a Pacer with sensible defaults func New() *Pacer { p := &Pacer{ - minSleep: 10 * time.Millisecond, - maxSleep: 2 * time.Second, - decayConstant: 2, - retries: fs.Config.LowLevelRetries, - pacer: make(chan struct{}, 1), + minSleep: 10 * time.Millisecond, + maxSleep: 2 * time.Second, + decayConstant: 2, + attackConstant: 1, + retries: fs.Config.LowLevelRetries, + pacer: make(chan struct{}, 1), } p.sleepTime = p.minSleep p.SetPacer(DefaultPacer) @@ -116,7 +118,7 @@ func (p *Pacer) SetMaxConnections(n int) *Pacer { // This is the speed the time falls back to the minimum after errors // have occurred. // -// bigger for slower decay, exponential +// bigger for slower decay, exponential. 1 is halve, 0 is go straight to minimum func (p *Pacer) SetDecayConstant(decay uint) *Pacer { p.mu.Lock() defer p.mu.Unlock() @@ -124,6 +126,19 @@ func (p *Pacer) SetDecayConstant(decay uint) *Pacer { return p } +// SetAttackConstant sets the attack constant for the pacer +// +// This is the speed the time grows from the minimum after errors have +// occurred. +// +// bigger for slower attack, 1 is double, 0 is go straight to maximum +func (p *Pacer) SetAttackConstant(attack uint) *Pacer { + p.mu.Lock() + defer p.mu.Unlock() + p.attackConstant = attack + return p +} + // SetRetries sets the max number of tries for Call func (p *Pacer) SetRetries(retries int) *Pacer { p.mu.Lock() @@ -185,7 +200,11 @@ func (p *Pacer) beginCall() { func (p *Pacer) defaultPacer(retry bool) { oldSleepTime := p.sleepTime if retry { - p.sleepTime *= 2 + if p.attackConstant == 0 { + p.sleepTime = p.maxSleep + } else { + p.sleepTime = (p.sleepTime << p.attackConstant) / ((1 << p.attackConstant) - 1) + } if p.sleepTime > p.maxSleep { p.sleepTime = p.maxSleep } diff --git a/pacer/pacer_test.go b/pacer/pacer_test.go index 987d2bd42..d60d76fa6 100644 --- a/pacer/pacer_test.go +++ b/pacer/pacer_test.go @@ -27,6 +27,9 @@ func TestNew(t *testing.T) { if p.decayConstant != 2 { t.Errorf("decayConstant") } + if p.attackConstant != 1 { + t.Errorf("attackConstant") + } if cap(p.pacer) != 1 { t.Errorf("pacer 1") } @@ -85,6 +88,58 @@ func TestSetDecayConstant(t *testing.T) { } } +func TestDecay(t *testing.T) { + p := New().SetMinSleep(time.Microsecond).SetPacer(DefaultPacer).SetMaxSleep(time.Second) + for _, test := range []struct { + in time.Duration + attackConstant uint + want time.Duration + }{ + {8 * time.Millisecond, 1, 4 * time.Millisecond}, + {1 * time.Millisecond, 0, time.Microsecond}, + {1 * time.Millisecond, 2, (3 * time.Millisecond) / 4}, + {1 * time.Millisecond, 3, (7 * time.Millisecond) / 8}, + } { + p.sleepTime = test.in + p.SetDecayConstant(test.attackConstant) + p.defaultPacer(false) + got := p.sleepTime + if got != test.want { + t.Errorf("bad sleep want %v got %v", test.want, got) + } + } +} + +func TestSetAttackConstant(t *testing.T) { + p := New().SetAttackConstant(19) + if p.attackConstant != 19 { + t.Errorf("didn't set") + } +} + +func TestAttack(t *testing.T) { + p := New().SetMinSleep(time.Microsecond).SetPacer(DefaultPacer).SetMaxSleep(time.Second) + for _, test := range []struct { + in time.Duration + attackConstant uint + want time.Duration + }{ + {1 * time.Millisecond, 1, 2 * time.Millisecond}, + {1 * time.Millisecond, 0, time.Second}, + {1 * time.Millisecond, 2, (4 * time.Millisecond) / 3}, + {1 * time.Millisecond, 3, (8 * time.Millisecond) / 7}, + } { + p.sleepTime = test.in + p.SetAttackConstant(test.attackConstant) + p.defaultPacer(true) + got := p.sleepTime + if got != test.want { + t.Errorf("bad sleep want %v got %v", test.want, got) + } + } + +} + func TestSetRetries(t *testing.T) { p := New().SetRetries(18) if p.retries != 18 {