-
Notifications
You must be signed in to change notification settings - Fork 12
/
bully.go
233 lines (209 loc) · 5.6 KB
/
bully.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package bully
import (
"encoding/gob"
"fmt"
"io"
"log"
"net"
"sync"
"time"
)
// Bully is a `struct` representing a single node used by the `Bully Algorithm`.
//
// NOTE: More details about the `Bully algorithm` can be found here
// https://en.wikipedia.org/wiki/Bully_algorithm .
type Bully struct {
*net.TCPListener
ID string
addr string
coordinator string
peers Peers
mu *sync.RWMutex
receiveChan chan Message
electionChan chan Message
}
// NewBully returns a new `Bully` or an `error`.
//
// NOTE: All connections to `Peer`s are established during this function.
//
// NOTE: The `proto` value can be one of this list: `tcp`, `tcp4`, `tcp6`.
func NewBully(ID, addr, proto string, peers map[string]string) (*Bully, error) {
b := &Bully{
ID: ID,
addr: addr,
coordinator: ID,
peers: NewPeerMap(),
mu: &sync.RWMutex{},
electionChan: make(chan Message, 1),
receiveChan: make(chan Message),
}
if err := b.Listen(proto, addr); err != nil {
return nil, fmt.Errorf("NewBully: %v", err)
}
b.Connect(proto, peers)
return b, nil
}
// receive is a helper function handling communication between `Peer`s
// and `b`. It creates a `gob.Decoder` and a from a `io.ReadCloser`. Each
// `Message` received that is not of type `CLOSE` or `OK` is pushed to
// `b.receiveChan`.
//
// NOTE: this function is an infinite loop.
func (b *Bully) receive(rwc io.ReadCloser) {
var msg Message
dec := gob.NewDecoder(rwc)
for {
if err := dec.Decode(&msg); err == io.EOF || msg.Type == CLOSE {
_ = rwc.Close()
if msg.PeerID == b.Coordinator() {
b.peers.Delete(msg.PeerID)
b.SetCoordinator(b.ID)
b.Elect()
}
break
} else if msg.Type == OK {
select {
case b.electionChan <- msg:
continue
case <-time.After(200 * time.Millisecond):
continue
}
} else {
b.receiveChan <- msg
}
}
}
// listen is a helper function that spawns goroutines handling new `Peers`
// connections to `b`'s socket.
//
// NOTE: this function is an infinite loop.
func (b *Bully) listen() {
for {
conn, err := b.AcceptTCP()
if err != nil {
log.Printf("listen: %v", err)
continue
}
go b.receive(conn)
}
}
// Listen makes `b` listens on the address `addr` provided using the protocol
// `proto` and returns an `error` if something occurs.
func (b *Bully) Listen(proto, addr string) error {
laddr, err := net.ResolveTCPAddr(proto, addr)
if err != nil {
return fmt.Errorf("Listen: %v", err)
}
b.TCPListener, err = net.ListenTCP(proto, laddr)
if err != nil {
return fmt.Errorf("Listen: %v", err)
}
go b.listen()
return nil
}
// connect is a helper function that resolves the tcp address `addr` and try
// to establish a tcp connection using the protocol `proto`. The established
// connection is set to `b.peers[ID]` or the function returns an `error`
// if something occurs.
//
// NOTE: In the case `ID` already exists in `b.peers`, the new connection
// replaces the old one.
func (b *Bully) connect(proto, addr, ID string) error {
raddr, err := net.ResolveTCPAddr(proto, addr)
if err != nil {
return fmt.Errorf("connect: %v", err)
}
sock, err := net.DialTCP(proto, nil, raddr)
if err != nil {
return fmt.Errorf("connect: %v", err)
}
b.peers.Add(ID, addr, sock)
return nil
}
// Connect performs a connection to the remote `Peer`s.
func (b *Bully) Connect(proto string, peers map[string]string) {
for ID, addr := range peers {
if b.ID == ID {
continue
}
if err := b.connect(proto, addr, ID); err != nil {
log.Printf("Connect: %v", err)
b.peers.Delete(ID)
}
}
}
// Send sends a `bully.Message` of type `what` to `b.peer[to]` at the address
// `addr`. If no connection is reachable at `addr` or if `b.peer[to]` does not
// exist, the function retries five times and returns an `error` if it does not
// succeed.
func (b *Bully) Send(to, addr string, what int) error {
maxRetries := 5
if !b.peers.Find(to) {
_ = b.connect("tcp4", addr, to)
}
for attempts := 0; ; attempts++ {
err := b.peers.Write(to, &Message{PeerID: b.ID, Addr: b.addr, Type: what})
if err == nil {
break
}
if attempts > maxRetries && err != nil {
return fmt.Errorf("Send: %v", err)
}
_ = b.connect("tcp4", addr, to)
time.Sleep(10 * time.Millisecond)
}
return nil
}
// SetCoordinator sets `ID` as the new `b.coordinator` if `ID` is greater than
// `b.coordinator` or equal to `b.ID`.
//
// NOTE: This function is thread-safe.
func (b *Bully) SetCoordinator(ID string) {
b.mu.Lock()
defer b.mu.Unlock()
if ID > b.coordinator || ID == b.ID {
b.coordinator = ID
}
}
// Coordinator returns `b.coordinator`.
//
// NOTE: This function is thread-safe.
func (b *Bully) Coordinator() string {
b.mu.RLock()
defer b.mu.RUnlock()
return b.coordinator
}
// Elect handles the leader election mechanism of the `Bully algorithm`.
func (b *Bully) Elect() {
for _, rBully := range b.peers.PeerData() {
if rBully.ID > b.ID {
_ = b.Send(rBully.ID, rBully.Addr, ELECTION)
}
}
select {
case <-b.electionChan:
return
case <-time.After(time.Second):
b.SetCoordinator(b.ID)
for _, rBully := range b.peers.PeerData() {
_ = b.Send(rBully.ID, rBully.Addr, COORDINATOR)
}
return
}
}
// Run launches the two main goroutine. The first one is tied to the
// execution of `workFunc` while the other one is the `Bully algorithm`.
//
// NOTE: This function is an infinite loop.
func (b *Bully) Run(workFunc func()) {
go workFunc()
b.Elect()
for msg := range b.receiveChan {
if msg.Type == ELECTION && msg.PeerID < b.ID {
_ = b.Send(msg.PeerID, msg.Addr, OK)
b.Elect()
} else if msg.Type == COORDINATOR {
b.SetCoordinator(msg.PeerID)
}
}
}