Internal API cleanup, vote timing jitter

This commit is contained in:
Ian Gulliver
2023-06-03 22:31:09 -07:00
parent 3cebbc3425
commit d641fdca40
2 changed files with 38 additions and 31 deletions

View File

@@ -20,8 +20,8 @@ type Candidate struct {
numVoters int numVoters int
signingKey []byte signingKey []byte
stop chan<- bool stop chan bool
done <-chan bool done chan bool
resp voteResponse resp voteResponse
c chan<- CandidateState c chan<- CandidateState
@@ -38,8 +38,6 @@ var (
) )
func NewCandidate(numVoters int, signingKey string) *Candidate { func NewCandidate(numVoters int, signingKey string) *Candidate {
stop := make(chan bool)
done := make(chan bool)
change := make(chan CandidateState, 100) change := make(chan CandidateState, 100)
c := &Candidate{ c := &Candidate{
@@ -47,15 +45,15 @@ func NewCandidate(numVoters int, signingKey string) *Candidate {
numVoters: numVoters, numVoters: numVoters,
signingKey: []byte(signingKey), signingKey: []byte(signingKey),
votes: map[string]*vote{}, votes: map[string]*vote{},
stop: stop, stop: make(chan bool),
done: done, done: make(chan bool),
c: change, c: change,
resp: voteResponse{ resp: voteResponse{
CandidateID: uniuri.New(), CandidateID: uniuri.New(),
}, },
} }
go c.loop(stop, done) go c.loop()
return c return c
} }
@@ -237,14 +235,14 @@ func (c *Candidate) update(state CandidateState) {
c.c <- state c.c <- state
} }
func (c *Candidate) loop(stop <-chan bool, done chan<- bool) { func (c *Candidate) loop() {
t := time.NewTicker(1 * time.Second) t := time.NewTicker(1 * time.Second)
defer t.Stop() defer t.Stop()
defer close(done) defer close(c.done)
for { for {
select { select {
case <-stop: case <-c.stop:
return return
case <-t.C: case <-t.C:

View File

@@ -4,6 +4,7 @@ import (
"crypto/hmac" "crypto/hmac"
"encoding/json" "encoding/json"
"log" "log"
"math/rand"
"time" "time"
"github.com/dchest/uniuri" "github.com/dchest/uniuri"
@@ -12,12 +13,16 @@ import (
) )
type Voter struct { type Voter struct {
// used by user and loop() goroutines
update chan time.Duration
done chan bool
// used by loop() goroutine only
client *resty.Client client *resty.Client
signingKey []byte signingKey []byte
update chan<- time.Duration
done <-chan bool
vote vote vote vote
candidates []*Candidate candidates []*Candidate
period time.Duration
} }
type vote struct { type vote struct {
@@ -36,22 +41,20 @@ type voteResponse struct {
} }
func NewVoter(url string, signingKey string) *Voter { func NewVoter(url string, signingKey string) *Voter {
update := make(chan time.Duration)
done := make(chan bool)
v := &Voter{ v := &Voter{
client: resty.New(). client: resty.New().
SetCloseConnection(true). SetCloseConnection(true).
SetBaseURL(url), SetBaseURL(url),
signingKey: []byte(signingKey), signingKey: []byte(signingKey),
update: update, update: make(chan time.Duration),
done: done, done: make(chan bool),
vote: vote{ vote: vote{
VoterID: uniuri.New(), VoterID: uniuri.New(),
}, },
period: 5 * time.Second,
} }
go v.loop(update, done) go v.loop()
return v return v
} }
@@ -65,40 +68,42 @@ func (v *Voter) AddCandidate(c *Candidate) {
v.candidates = append(v.candidates, c) v.candidates = append(v.candidates, c)
} }
func (v *Voter) loop(update <-chan time.Duration, done chan<- bool) { func (v *Voter) loop() {
// TODO: Need a JitterTicker defer close(v.done)
t := time.NewTicker(5 * time.Second)
defer t.Stop()
defer close(done)
for { for {
if !v.poll(update, t) { if !v.poll() {
break break
} }
} }
} }
func (v *Voter) poll(update <-chan time.Duration, t *time.Ticker) bool { func (v *Voter) poll() bool {
// mean: v.period, max: v.period*2
t := time.NewTimer(randDurationN(v.period * 2))
defer t.Stop()
t2 := &time.Timer{} t2 := &time.Timer{}
if v.vote.NumPollsSinceChange <= 10 { if v.vote.NumPollsSinceChange <= 10 {
t2 = time.NewTimer(100 * time.Millisecond) // mean: 100ms, max: 200ms
t2 = time.NewTimer(randDurationN(100 * time.Millisecond * 2))
defer t2.Stop() defer t2.Stop()
} }
select { select {
case <-t2.C:
v.sendVote()
case <-t.C: case <-t.C:
v.sendVote() v.sendVote()
case period, ok := <-update: case <-t2.C:
v.sendVote()
case period, ok := <-v.update:
if !ok { if !ok {
return false return false
} }
t.Reset(period) v.period = period
} }
return true return true
@@ -169,3 +174,7 @@ func (v *Voter) sendVote() {
func (v *Voter) log(format string, args ...any) { func (v *Voter) log(format string, args ...any) {
log.Printf("[voter] "+format, args...) log.Printf("[voter] "+format, args...)
} }
func randDurationN(n time.Duration) time.Duration {
return time.Duration(rand.Int63n(int64(n))) //nolint:gosec
}