For my work I need to make some modifications to an in-house raft library (
https://raft.github.io/). Without delving too deep into the algorithm, one of the stages in the algorithm is a "candidate" goroutine that performs an assessment of whether it should be able to transition to a "leader" state.
There are some rules around how the candidate performs this assessment and one of the rules is to periodically run an "election" where the candidate seeks votes from its peers and if it receives the majority of votes, it declares itself a leader. The algorithm is a bit more nuanced to this but for the sake of simplification I am focusing just on the part where the election proceeds as follows:
1. Starts the election process.
2. Sets an election timer, which periodically expires, if the candidate has not received any votes during that period, it will reset the deadline and start the election all over again.
With that in mind I modeled my implementation as follows:
func (f *candidate) startElection(ctx context.Context) <-chan struct{} {
//Channel that signals to the caller when the election is done
electionCompletionChan := make(chan struct{})
go func() {
//Instead of busy-looping, running the poll logic at predefined intervals
ticker := time.NewTicker(config.PollDuration * time.Second)
defer ticker.Stop()
//Reset the election deadline. SetDeadline takes the current time and sets a random offset //in the future
f.raftTimer.SetDeadline(time.Now())
//callElection makes the RPC to its peers requesting votes and upon completion,
//returns its status to the voteStatusChan channel
voteStatusChan := f.callElection()
//Polling
for tick := range ticker.C {
select {
//The caller called cancel on the context
case <-ctx.Done():
log.Println("Cancelling election")
//Send an empty struct signaling the end of the election process
electionCompletionChan <- struct{}{}
return
//Checking whether all the votes have been received
case voteStatus := <-voteStatusChan:
{
switch voteStatus {
//Majority of peers agreed that the candidate should be the leader
case voter.Leader:
//Flip the state to leader
f.leaderTrigger <- LeaderTrigger{}
//Conclude the election process
electionCompletionChan <- struct{}{}
return
default:
//Split vote or loss: Do nothing, re-election will be triggered
}
}
default:
//Check whether the election deadline was exceeded
if tick.After(f.raftTimer.GetDeadline()) {
//Reset the deadline
f.raftTimer.SetDeadline(tick)
//Once again make the callElection to redo the election
voteStatusChan = f.callElection()
}
}
}
}()
return electionCompletionChan
}
There are a few things that I am worried about in the above given snippet. The first thing is whether I am in alignment with golang's idioms while modeling this process using channels and the second thing is where I am resetting the voteStatusChan by creating channels within the poll loop. Something about creating new channels for every tick of the timer seems to be wasteful (I don't have a particularly nuanced understanding of how unbounded channel creations will tax the garbage collector or if there are other dangers lurking in the corner by taking this approach)
It will be great to get some feedback on what I may be doing wrong here.
Thanks