forked from TrueCloudLab/frostfs-node
[#1210] timers: Add IterationsTicker
It allows specifying duration in blocks and a desired number of handler calls in that period. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
parent
13af4e6046
commit
77d847dbea
2 changed files with 208 additions and 0 deletions
90
cmd/neofs-node/reputation/ticker/fixed.go
Normal file
90
cmd/neofs-node/reputation/ticker/fixed.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package ticker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IterationHandler is a callback of a certain block advance.
|
||||||
|
type IterationHandler func()
|
||||||
|
|
||||||
|
// IterationsTicker represents fixed tick number block timer.
|
||||||
|
//
|
||||||
|
// It can tick the blocks and perform certain actions
|
||||||
|
// on block time intervals.
|
||||||
|
type IterationsTicker struct {
|
||||||
|
m sync.Mutex
|
||||||
|
|
||||||
|
curr uint64
|
||||||
|
period uint64
|
||||||
|
|
||||||
|
times uint64
|
||||||
|
|
||||||
|
h IterationHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterationsTicker creates a new IterationsTicker.
|
||||||
|
//
|
||||||
|
// It guaranties that handler would be called the
|
||||||
|
// specified amount of times in the specified amount
|
||||||
|
// of blocks. After the last meaningful Tick IterationsTicker
|
||||||
|
// becomes no-op timer.
|
||||||
|
//
|
||||||
|
// Returns error only if times is greater than totalBlocks.
|
||||||
|
func NewIterationsTicker(totalBlocks uint64, times uint64, h IterationHandler) (*IterationsTicker, error) {
|
||||||
|
period := totalBlocks / times
|
||||||
|
|
||||||
|
if period == 0 {
|
||||||
|
return nil, fmt.Errorf("impossible to tick %d times in %d blocks",
|
||||||
|
times, totalBlocks,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var curr uint64
|
||||||
|
|
||||||
|
// try to make handler calls as rare as possible
|
||||||
|
if totalBlocks%times != 0 {
|
||||||
|
extraBlocks := (period+1)*times - totalBlocks
|
||||||
|
|
||||||
|
if period >= extraBlocks {
|
||||||
|
curr = extraBlocks + (period-extraBlocks)/2
|
||||||
|
period++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IterationsTicker{
|
||||||
|
curr: curr,
|
||||||
|
period: period,
|
||||||
|
times: times,
|
||||||
|
h: h,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tick ticks one block in the IterationsTicker.
|
||||||
|
//
|
||||||
|
// Returns `false` if the timer has finished its operations
|
||||||
|
// and there will be no more handler calls.
|
||||||
|
// Calling Tick after the returned `false` is safe, no-op
|
||||||
|
// and also returns `false`.
|
||||||
|
func (ft *IterationsTicker) Tick() bool {
|
||||||
|
ft.m.Lock()
|
||||||
|
defer ft.m.Unlock()
|
||||||
|
|
||||||
|
if ft.times == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ft.curr++
|
||||||
|
|
||||||
|
if ft.curr%ft.period == 0 {
|
||||||
|
ft.h()
|
||||||
|
|
||||||
|
ft.times--
|
||||||
|
|
||||||
|
if ft.times == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
118
cmd/neofs-node/reputation/ticker/fixed_test.go
Normal file
118
cmd/neofs-node/reputation/ticker/fixed_test.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package ticker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFixedTimer_Tick(t *testing.T) {
|
||||||
|
tests := [...]struct {
|
||||||
|
duration uint64
|
||||||
|
times uint64
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
duration: 20,
|
||||||
|
times: 4,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
duration: 6,
|
||||||
|
times: 6,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
duration: 10,
|
||||||
|
times: 6,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
duration: 5,
|
||||||
|
times: 6,
|
||||||
|
err: errors.New("impossible to tick 6 times in 5 blocks"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("duration:%d,times:%d", test.duration, test.times), func(t *testing.T) {
|
||||||
|
counter := uint64(0)
|
||||||
|
|
||||||
|
timer, err := NewIterationsTicker(test.duration, test.times, func() {
|
||||||
|
counter++
|
||||||
|
})
|
||||||
|
if test.err != nil {
|
||||||
|
require.EqualError(t, err, test.err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i := 0; i < int(test.duration); i++ {
|
||||||
|
if !timer.Tick() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, false, timer.Tick())
|
||||||
|
require.Equal(t, test.times, counter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixedTimer_RareCalls(t *testing.T) {
|
||||||
|
tests := [...]struct {
|
||||||
|
duration uint64
|
||||||
|
times uint64
|
||||||
|
firstCall uint64
|
||||||
|
period uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
duration: 11,
|
||||||
|
times: 6,
|
||||||
|
firstCall: 1,
|
||||||
|
period: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
duration: 11,
|
||||||
|
times: 4,
|
||||||
|
firstCall: 2,
|
||||||
|
period: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
duration: 20,
|
||||||
|
times: 3,
|
||||||
|
firstCall: 4,
|
||||||
|
period: 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("duration:%d,times:%d", test.duration, test.times), func(t *testing.T) {
|
||||||
|
var counter uint64
|
||||||
|
|
||||||
|
timer, err := NewIterationsTicker(test.duration, test.times, func() {
|
||||||
|
counter++
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
checked := false
|
||||||
|
|
||||||
|
for i := 1; i <= int(test.duration); i++ {
|
||||||
|
if !timer.Tick() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checked && counter == 1 {
|
||||||
|
require.Equal(t, test.firstCall, uint64(i))
|
||||||
|
checked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, false, timer.Tick())
|
||||||
|
require.Equal(t, test.times, counter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue