Add multiflight package
This makes it possible to invoke some function and monitor its return values. Said return values will be cached if they are unchanging, and updated more frequently if they start changing more frequently.
This commit is contained in:
Родитель
f0e3f3d555
Коммит
0bcd0044a5
|
@ -0,0 +1,148 @@
|
|||
package multiflight_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-container-networking/multiflight"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type MockDelayer struct {
|
||||
IncreaseF func(time.Time) time.Time
|
||||
DecreaseF func(time.Time) time.Time
|
||||
}
|
||||
|
||||
func (m *MockDelayer) Increase(t time.Time) time.Time {
|
||||
return m.IncreaseF(t)
|
||||
}
|
||||
|
||||
func (m *MockDelayer) Decrease(t time.Time) time.Time {
|
||||
return m.DecreaseF(t)
|
||||
}
|
||||
|
||||
type EqualInt int
|
||||
|
||||
func (e EqualInt) Equal(other EqualInt) bool {
|
||||
return int(e) == int(other)
|
||||
}
|
||||
|
||||
func TestRefresher(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
generator func(*int) func(context.Context) (EqualInt, error)
|
||||
delayer multiflight.Delayer
|
||||
exp []int
|
||||
numCalls int
|
||||
}{
|
||||
{
|
||||
"once",
|
||||
func(called *int) func(context.Context) (EqualInt, error) {
|
||||
return func(context.Context) (EqualInt, error) {
|
||||
defer func() { (*called)++ }()
|
||||
return EqualInt(42), nil
|
||||
}
|
||||
},
|
||||
&MockDelayer{
|
||||
IncreaseF: func(t time.Time) time.Time {
|
||||
return t.Add(1 * time.Hour)
|
||||
},
|
||||
DecreaseF: func(t time.Time) time.Time {
|
||||
return t
|
||||
},
|
||||
},
|
||||
[]int{42},
|
||||
1,
|
||||
},
|
||||
{
|
||||
"twice",
|
||||
func(called *int) func(context.Context) (EqualInt, error) {
|
||||
return func(context.Context) (EqualInt, error) {
|
||||
defer func() { (*called)++ }()
|
||||
return EqualInt(42), nil
|
||||
}
|
||||
},
|
||||
&MockDelayer{
|
||||
IncreaseF: func(t time.Time) time.Time {
|
||||
return t.Add(1 * time.Hour)
|
||||
},
|
||||
DecreaseF: func(t time.Time) time.Time {
|
||||
return t
|
||||
},
|
||||
},
|
||||
[]int{42, 42},
|
||||
2,
|
||||
},
|
||||
{
|
||||
"changes",
|
||||
func(called *int) func(context.Context) (EqualInt, error) {
|
||||
vals := []int{4, 8, 15, 16, 23, 42}
|
||||
return func(context.Context) (EqualInt, error) {
|
||||
defer func() { (*called)++ }()
|
||||
return EqualInt(vals[*called]), nil
|
||||
}
|
||||
},
|
||||
&MockDelayer{
|
||||
IncreaseF: func(t time.Time) time.Time {
|
||||
return t.Add(1 * time.Hour)
|
||||
},
|
||||
DecreaseF: func(t time.Time) time.Time {
|
||||
return t
|
||||
},
|
||||
},
|
||||
[]int{4, 8, 15, 16, 23, 42},
|
||||
6,
|
||||
},
|
||||
{
|
||||
"cache",
|
||||
func(called *int) func(context.Context) (EqualInt, error) {
|
||||
vals := []int{4, 4, 15, 16, 23, 42}
|
||||
return func(context.Context) (EqualInt, error) {
|
||||
defer func() { (*called)++ }()
|
||||
return EqualInt(vals[*called]), nil
|
||||
}
|
||||
},
|
||||
&MockDelayer{
|
||||
IncreaseF: func(t time.Time) time.Time {
|
||||
return t.Add(1 * time.Hour)
|
||||
},
|
||||
DecreaseF: func(t time.Time) time.Time {
|
||||
return t
|
||||
},
|
||||
},
|
||||
[]int{4, 4, 4},
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
called := 0
|
||||
rf := multiflight.NewRefresher(test.generator(&called))
|
||||
rf.Delayer = test.delayer
|
||||
|
||||
got := make([]int, len(test.exp))
|
||||
|
||||
for idx, _ := range test.exp {
|
||||
gotVal, err := rf.Get(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatal("unexpected error getting value: err:", err)
|
||||
}
|
||||
|
||||
got[idx] = int(gotVal)
|
||||
}
|
||||
|
||||
if !cmp.Equal(test.exp, got) {
|
||||
t.Error("received values differ from expected: diff:", cmp.Diff(test.exp, got))
|
||||
}
|
||||
|
||||
if test.numCalls != called {
|
||||
t.Error("unexpected number of calls: exp:", test.numCalls, "got:", called)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package multiflight
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Equalable[T any] interface {
|
||||
Equal(other T) bool
|
||||
}
|
||||
|
||||
func NewRefresher[T Equalable[T]](doFunc func(context.Context) (T, error)) *Refresher[T] {
|
||||
return &Refresher[T]{
|
||||
DoFunc: doFunc,
|
||||
}
|
||||
}
|
||||
|
||||
type Delayer interface {
|
||||
Increase(time.Time) time.Time
|
||||
Decrease(time.Time) time.Time
|
||||
}
|
||||
|
||||
type Refresher[T Equalable[T]] struct {
|
||||
DoFunc func(context.Context) (T, error)
|
||||
Delayer Delayer
|
||||
nextCall time.Time
|
||||
lastVal T
|
||||
}
|
||||
|
||||
func (r *Refresher[T]) Get(ctx context.Context) (T, error) {
|
||||
if time.Now().After(r.nextCall) {
|
||||
nextVal, err := r.DoFunc(ctx)
|
||||
if err != nil {
|
||||
return *new(T), err
|
||||
}
|
||||
|
||||
if r.lastVal.Equal(nextVal) {
|
||||
r.nextCall = r.Delayer.Increase(time.Now())
|
||||
}
|
||||
|
||||
r.lastVal = nextVal
|
||||
}
|
||||
return r.lastVal, nil
|
||||
}
|
Загрузка…
Ссылка в новой задаче