* [x] expononential cooldown of retries * [x] do not trigger sync by counts * [x] randomization of event poll intervalcreate-reload-action
parent
a1b01d5922
commit
b15d22c8cc
9 changed files with 227 additions and 6 deletions
@ -0,0 +1,65 @@ |
|||||||
|
// Copyright (c) 2020 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package store |
||||||
|
|
||||||
|
import "time" |
||||||
|
|
||||||
|
type cooldown struct { |
||||||
|
waitTimes []time.Duration |
||||||
|
waitIndex int |
||||||
|
lastTry time.Time |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cooldown) setExponentialWait(initial time.Duration, base int, maximum time.Duration) { |
||||||
|
waitTimes := []time.Duration{} |
||||||
|
t := initial |
||||||
|
if base > 1 { |
||||||
|
for t < maximum { |
||||||
|
waitTimes = append(waitTimes, t) |
||||||
|
t *= time.Duration(base) |
||||||
|
} |
||||||
|
} |
||||||
|
waitTimes = append(waitTimes, maximum) |
||||||
|
c.setWaitTimes(waitTimes...) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cooldown) setWaitTimes(newTimes ...time.Duration) { |
||||||
|
c.waitTimes = newTimes |
||||||
|
c.reset() |
||||||
|
} |
||||||
|
|
||||||
|
// isTooSoon™ returns whether the cooldown period is not yet over.
|
||||||
|
func (c *cooldown) isTooSoon() bool { |
||||||
|
if time.Since(c.lastTry) < c.waitTimes[c.waitIndex] { |
||||||
|
return true |
||||||
|
} |
||||||
|
c.lastTry = time.Now() |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cooldown) increaseWaitTime() { |
||||||
|
c.lastTry = time.Now() |
||||||
|
if c.waitIndex+1 < len(c.waitTimes) { |
||||||
|
c.waitIndex++ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cooldown) reset() { |
||||||
|
c.waitIndex = 0 |
||||||
|
c.lastTry = time.Time{} |
||||||
|
} |
||||||
@ -0,0 +1,133 @@ |
|||||||
|
// Copyright (c) 2020 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package store |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
) |
||||||
|
|
||||||
|
func TestCooldownExponentialWait(t *testing.T) { |
||||||
|
ms := time.Millisecond |
||||||
|
sec := time.Second |
||||||
|
|
||||||
|
testData := []struct { |
||||||
|
haveInitial, haveMax time.Duration |
||||||
|
haveBase int |
||||||
|
wantWaitTimes []time.Duration |
||||||
|
}{ |
||||||
|
{ |
||||||
|
haveInitial: 1 * sec, |
||||||
|
haveBase: 0, |
||||||
|
haveMax: 0 * sec, |
||||||
|
wantWaitTimes: []time.Duration{0 * sec}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
haveInitial: 0 * sec, |
||||||
|
haveBase: 1, |
||||||
|
haveMax: 0 * sec, |
||||||
|
wantWaitTimes: []time.Duration{0 * sec}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
haveInitial: 0 * sec, |
||||||
|
haveBase: 0, |
||||||
|
haveMax: 1 * sec, |
||||||
|
wantWaitTimes: []time.Duration{1 * sec}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
haveInitial: 0 * sec, |
||||||
|
haveBase: 1, |
||||||
|
haveMax: 1 * sec, |
||||||
|
wantWaitTimes: []time.Duration{1 * sec}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
haveInitial: 1 * sec, |
||||||
|
haveBase: 0, |
||||||
|
haveMax: 1 * sec, |
||||||
|
wantWaitTimes: []time.Duration{1 * sec}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
haveInitial: 1 * sec, |
||||||
|
haveBase: 2, |
||||||
|
haveMax: 1 * sec, |
||||||
|
wantWaitTimes: []time.Duration{1 * sec}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
haveInitial: 500 * ms, |
||||||
|
haveBase: 2, |
||||||
|
haveMax: 5 * sec, |
||||||
|
wantWaitTimes: []time.Duration{500 * ms, 1 * sec, 2 * sec, 4 * sec, 5 * sec}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var testCooldown cooldown |
||||||
|
|
||||||
|
for _, td := range testData { |
||||||
|
testCooldown.setExponentialWait(td.haveInitial, td.haveBase, td.haveMax) |
||||||
|
assert.Equal(t, td.wantWaitTimes, testCooldown.waitTimes) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestCooldownIncreaseAndReset(t *testing.T) { |
||||||
|
var testCooldown cooldown |
||||||
|
testCooldown.setWaitTimes(1*time.Second, 2*time.Second, 3*time.Second) |
||||||
|
assert.Equal(t, 0, testCooldown.waitIndex) |
||||||
|
|
||||||
|
assert.False(t, testCooldown.isTooSoon()) |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
assert.Equal(t, 0, testCooldown.waitIndex) |
||||||
|
|
||||||
|
testCooldown.reset() |
||||||
|
assert.Equal(t, 0, testCooldown.waitIndex) |
||||||
|
|
||||||
|
assert.False(t, testCooldown.isTooSoon()) |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
assert.Equal(t, 0, testCooldown.waitIndex) |
||||||
|
|
||||||
|
// increase at least N+1 times to check overflow
|
||||||
|
testCooldown.increaseWaitTime() |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
testCooldown.increaseWaitTime() |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
testCooldown.increaseWaitTime() |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
testCooldown.increaseWaitTime() |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
|
||||||
|
assert.Equal(t, 2, testCooldown.waitIndex) |
||||||
|
} |
||||||
|
|
||||||
|
func TestCooldownNotSooner(t *testing.T) { |
||||||
|
var testCooldown cooldown |
||||||
|
waitTime := 100 * time.Millisecond |
||||||
|
retries := int64(10) |
||||||
|
retryWait := time.Duration(waitTime.Milliseconds()/retries) * time.Millisecond |
||||||
|
testCooldown.setWaitTimes(waitTime) |
||||||
|
|
||||||
|
// first time it should never be too soon
|
||||||
|
assert.False(t, testCooldown.isTooSoon()) |
||||||
|
// these retries should be too soon
|
||||||
|
for i := retries; i > 0; i-- { |
||||||
|
assert.True(t, testCooldown.isTooSoon()) |
||||||
|
time.Sleep(retryWait) |
||||||
|
} |
||||||
|
// after given wait time it shouldn't be soon anymore
|
||||||
|
assert.False(t, testCooldown.isTooSoon()) |
||||||
|
} |
||||||
Loading…
Reference in new issue