Skip to content
Snippets Groups Projects
Commit 4f535c81 authored by Benjamin Wenger's avatar Benjamin Wenger
Browse files

Merge remote-tracking branch 'origin/XX-4279/ChannelsDatabaseEncryption' into...

Merge remote-tracking branch 'origin/XX-4279/ChannelsDatabaseEncryption' into XX-4279/ChannelsDatabaseEncryption
parents 12558695 688c766e
No related branches found
No related tags found
3 merge requests!510Release,!422Add cipher bindings,!340Project/channels
package attempts package attempts
import ( import (
"fmt"
"sort" "sort"
"strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
) )
const maxHistogramSize = 100 const (
const minElements = 3 maxHistogramSize = 100
const percentileNumerator = 66 minElements = 3
const percentileDenominator = 99 percentileNumerator = 66
const percentileDenominatorOffset = 49 percentileDenominator = 99
percentileDenominatorOffset = 49
optimalAttemptsInitValue = -1
)
// SendAttemptTracker tracks the number of attempts it took to send a cMix
// message in order to predict how many attempt are needed.
type SendAttemptTracker interface { type SendAttemptTracker interface {
// SubmitProbeAttempt feeds the number of attempts it took to send a cMix
// message into the tracker and updates the optimal number of attempts.
SubmitProbeAttempt(numAttemptsUntilSuccessful int) SubmitProbeAttempt(numAttemptsUntilSuccessful int)
// GetOptimalNumAttempts returns the number of optimal sends. If there is
// insufficient data to calculate, then ready is false.
GetOptimalNumAttempts() (attempts int, ready bool) GetOptimalNumAttempts() (attempts int, ready bool)
} }
// sendAttempts tracks the number of attempts to send a cMix message.
type sendAttempts struct { type sendAttempts struct {
lock sync.Mutex
numAttempts []int
currentIndex int
isFull bool
optimalAttempts *int32 optimalAttempts *int32
isFull bool
currentIndex int
numAttempts []int
lock sync.Mutex
} }
// NewSendAttempts initialises a new SendAttemptTracker.
func NewSendAttempts() SendAttemptTracker { func NewSendAttempts() SendAttemptTracker {
optimalAttempts := int32(-1) optimalAttempts := int32(optimalAttemptsInitValue)
sa := &sendAttempts{ sa := &sendAttempts{
numAttempts: make([]int, maxHistogramSize),
currentIndex: 0,
isFull: false,
optimalAttempts: &optimalAttempts, optimalAttempts: &optimalAttempts,
isFull: false,
currentIndex: 0,
numAttempts: make([]int, maxHistogramSize),
} }
return sa return sa
} }
func (sa *sendAttempts) SubmitProbeAttempt(a int) { // SubmitProbeAttempt feeds the number of attempts it took to send a cMix
// message into the tracker and updates the optimal number of attempts.
func (sa *sendAttempts) SubmitProbeAttempt(numAttemptsUntilSuccessful int) {
sa.lock.Lock() sa.lock.Lock()
defer sa.lock.Unlock() defer sa.lock.Unlock()
sa.numAttempts[sa.currentIndex] = a sa.numAttempts[sa.currentIndex] = numAttemptsUntilSuccessful
sa.currentIndex += 1 sa.currentIndex++
if sa.currentIndex == len(sa.numAttempts) { if sa.currentIndex == len(sa.numAttempts) {
sa.currentIndex = 0 sa.currentIndex = 0
sa.isFull = true sa.isFull = true
...@@ -52,16 +69,19 @@ func (sa *sendAttempts) SubmitProbeAttempt(a int) { ...@@ -52,16 +69,19 @@ func (sa *sendAttempts) SubmitProbeAttempt(a int) {
sa.computeOptimalUnsafe() sa.computeOptimalUnsafe()
} }
// GetOptimalNumAttempts returns the number of optimal sends. If there is
// insufficient data to calculate, then ready is false.
func (sa *sendAttempts) GetOptimalNumAttempts() (attempts int, ready bool) { func (sa *sendAttempts) GetOptimalNumAttempts() (attempts int, ready bool) {
optimalAttempts := atomic.LoadInt32(sa.optimalAttempts) optimalAttempts := atomic.LoadInt32(sa.optimalAttempts)
if optimalAttempts == -1 { if optimalAttempts == optimalAttemptsInitValue {
return 0, false return 0, false
} }
return int(optimalAttempts), true return int(optimalAttempts), true
} }
// computeOptimalUnsafe updates the optimal send attempts.
func (sa *sendAttempts) computeOptimalUnsafe() { func (sa *sendAttempts) computeOptimalUnsafe() {
toCopy := maxHistogramSize toCopy := maxHistogramSize
if !sa.isFull { if !sa.isFull {
...@@ -69,16 +89,28 @@ func (sa *sendAttempts) computeOptimalUnsafe() { ...@@ -69,16 +89,28 @@ func (sa *sendAttempts) computeOptimalUnsafe() {
return return
} }
toCopy = sa.currentIndex toCopy = sa.currentIndex
} }
histoCopy := make([]int, toCopy) histogramCopy := make([]int, toCopy)
copy(histoCopy, sa.numAttempts[:toCopy]) copy(histogramCopy, sa.numAttempts[:toCopy])
sort.Ints(histogramCopy)
sort.Slice(histoCopy, func(i, j int) bool {
return histoCopy[i] < histoCopy[j]
})
optimal := histoCopy[((toCopy*percentileNumerator)+percentileDenominatorOffset)/percentileDenominator] i := ((toCopy * percentileNumerator) + percentileDenominatorOffset) /
percentileDenominator
optimal := histogramCopy[i]
atomic.StoreInt32(sa.optimalAttempts, int32(optimal)) atomic.StoreInt32(sa.optimalAttempts, int32(optimal))
} }
// String prints the values in the sendAttempts in a human-readable form for
// debugging and logging purposes. This function adheres to the fmt.Stringer
// interface.
func (sa *sendAttempts) String() string {
fields := []string{
"optimalAttempts:" + strconv.Itoa(int(atomic.LoadInt32(sa.optimalAttempts))),
"isFull:" + strconv.FormatBool(sa.isFull),
"currentIndex:" + strconv.Itoa(sa.currentIndex),
"numAttempts:" + fmt.Sprintf("%d", sa.numAttempts),
}
return "{" + strings.Join(fields, " ") + "}"
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// Use of this source code is governed by a license that can be found in the //
// LICENSE file. //
////////////////////////////////////////////////////////////////////////////////
package attempts
import (
"math/rand"
"reflect"
"testing"
)
// Tests that NewSendAttempts returns a new sendAttempts with the expected
// fields.
func TestNewSendAttempts(t *testing.T) {
optimalAttempts := int32(optimalAttemptsInitValue)
expected := &sendAttempts{
optimalAttempts: &optimalAttempts,
isFull: false,
currentIndex: 0,
numAttempts: make([]int, maxHistogramSize),
}
sa := NewSendAttempts()
if !reflect.DeepEqual(expected, sa) {
t.Errorf("New SendAttemptTracker does not match expected."+
"\nexpected: %+v\nreceivedL %+v", expected, sa)
}
}
// Tests that sendAttempts.SubmitProbeAttempt properly increments and stores the
// attempts.
func Test_sendAttempts_SubmitProbeAttempt(t *testing.T) {
sa := NewSendAttempts().(*sendAttempts)
for i := 0; i < maxHistogramSize+20; i++ {
sa.SubmitProbeAttempt(i)
if sa.currentIndex != (i+1)%maxHistogramSize {
t.Errorf("Incorrect currentIndex (%d).\nexpected: %d\nreceived: %d",
i, (i+1)%maxHistogramSize, sa.currentIndex)
} else if sa.numAttempts[i%maxHistogramSize] != i {
t.Errorf("Incorrect numAttempts at %d.\nexpected: %d\nreceived: %d",
i, i, sa.numAttempts[i%maxHistogramSize])
} else if i > maxHistogramSize && !sa.isFull {
t.Errorf("Should be marked full when numAttempts > %d.",
maxHistogramSize)
}
}
}
// Tests sendAttempts.GetOptimalNumAttempts returns numbers close to 70% of the
// average of attempts feeding in.
func Test_sendAttempts_GetOptimalNumAttempts(t *testing.T) {
prng := rand.New(rand.NewSource(42))
sa := NewSendAttempts().(*sendAttempts)
attempts, ready := sa.GetOptimalNumAttempts()
if ready {
t.Errorf("Marked ready when no attempts have been made.")
} else if attempts != 0 {
t.Errorf("Incorrect number of attempt.\nexpected: %d\nreceived: %d",
0, attempts)
}
const n = 100
factor := (n * 7) / 10
for i := 0; i < 500; i++ {
sa.SubmitProbeAttempt(prng.Intn(n))
attempts, ready = sa.GetOptimalNumAttempts()
if (sa.currentIndex < minElements && !sa.isFull) && ready {
t.Errorf("Ready when less than %d attempts made (%d).",
minElements, i)
} else if sa.currentIndex >= minElements {
if !ready {
t.Errorf("Not ready when more than %d attempts made (%d).",
minElements, i)
} else if attempts < factor-25 || attempts > factor+25 {
t.Errorf("Attempts is not close to average (%d)."+
"\naverage: %d\nattempts: %d", i, factor, attempts)
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment