From d641fdca405c573682879aed203b918729466444 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 3 Jun 2023 22:31:09 -0700 Subject: [PATCH] Internal API cleanup, vote timing jitter --- candidate.go | 18 ++++++++---------- voter.go | 51 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/candidate.go b/candidate.go index 64fd6ad..df2d09e 100644 --- a/candidate.go +++ b/candidate.go @@ -20,8 +20,8 @@ type Candidate struct { numVoters int signingKey []byte - stop chan<- bool - done <-chan bool + stop chan bool + done chan bool resp voteResponse c chan<- CandidateState @@ -38,8 +38,6 @@ var ( ) func NewCandidate(numVoters int, signingKey string) *Candidate { - stop := make(chan bool) - done := make(chan bool) change := make(chan CandidateState, 100) c := &Candidate{ @@ -47,15 +45,15 @@ func NewCandidate(numVoters int, signingKey string) *Candidate { numVoters: numVoters, signingKey: []byte(signingKey), votes: map[string]*vote{}, - stop: stop, - done: done, + stop: make(chan bool), + done: make(chan bool), c: change, resp: voteResponse{ CandidateID: uniuri.New(), }, } - go c.loop(stop, done) + go c.loop() return c } @@ -237,14 +235,14 @@ func (c *Candidate) update(state CandidateState) { c.c <- state } -func (c *Candidate) loop(stop <-chan bool, done chan<- bool) { +func (c *Candidate) loop() { t := time.NewTicker(1 * time.Second) defer t.Stop() - defer close(done) + defer close(c.done) for { select { - case <-stop: + case <-c.stop: return case <-t.C: diff --git a/voter.go b/voter.go index d4a98c3..254fcb7 100644 --- a/voter.go +++ b/voter.go @@ -4,6 +4,7 @@ import ( "crypto/hmac" "encoding/json" "log" + "math/rand" "time" "github.com/dchest/uniuri" @@ -12,12 +13,16 @@ import ( ) type Voter struct { + // used by user and loop() goroutines + update chan time.Duration + done chan bool + + // used by loop() goroutine only client *resty.Client signingKey []byte - update chan<- time.Duration - done <-chan bool vote vote candidates []*Candidate + period time.Duration } type vote struct { @@ -36,22 +41,20 @@ type voteResponse struct { } func NewVoter(url string, signingKey string) *Voter { - update := make(chan time.Duration) - done := make(chan bool) - v := &Voter{ client: resty.New(). SetCloseConnection(true). SetBaseURL(url), signingKey: []byte(signingKey), - update: update, - done: done, + update: make(chan time.Duration), + done: make(chan bool), vote: vote{ VoterID: uniuri.New(), }, + period: 5 * time.Second, } - go v.loop(update, done) + go v.loop() return v } @@ -65,40 +68,42 @@ func (v *Voter) AddCandidate(c *Candidate) { v.candidates = append(v.candidates, c) } -func (v *Voter) loop(update <-chan time.Duration, done chan<- bool) { - // TODO: Need a JitterTicker - t := time.NewTicker(5 * time.Second) - defer t.Stop() - defer close(done) +func (v *Voter) loop() { + defer close(v.done) for { - if !v.poll(update, t) { + if !v.poll() { 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{} 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() } select { - case <-t2.C: - v.sendVote() - case <-t.C: v.sendVote() - case period, ok := <-update: + case <-t2.C: + v.sendVote() + + case period, ok := <-v.update: if !ok { return false } - t.Reset(period) + v.period = period } return true @@ -169,3 +174,7 @@ func (v *Voter) sendVote() { func (v *Voter) log(format string, args ...any) { log.Printf("[voter] "+format, args...) } + +func randDurationN(n time.Duration) time.Duration { + return time.Duration(rand.Int63n(int64(n))) //nolint:gosec +}