REDESIGN MP2 IN GO
This commit is contained in:
1 parent
2fa715dfc0
commit
0e75fdc050
10 files changed
+327
-11
No files matched your search
@@ -0,0 +1,284 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
"math/rand"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// this file handles gossip protocol from MP2
|
||||
// thread 1 - handles receiving gossip
|
||||
// thread 2 - handles sending gossips and updating heart beat
|
||||
// thread 3 - handles failure detection
|
||||
|
||||
type Member_ID struct {
|
||||
ip string
|
||||
port int
|
||||
time time.Time
|
||||
}
|
||||
|
||||
type MemberInfo struct {
|
||||
heart_beat int
|
||||
state string
|
||||
incarnation_counter int
|
||||
local_failed_since *time.Time
|
||||
local_last_seen *time.Time
|
||||
}
|
||||
|
||||
type MembershipService struct {
|
||||
members_list map[Member_ID]MemberInfo
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
type GossipMessageType int
|
||||
|
||||
|
||||
type GossipMessage struct {
|
||||
Type string
|
||||
Senders_id Member_ID
|
||||
Members map[Member_ID]MemberInfo
|
||||
}
|
||||
|
||||
func (m *MembershipService) get_current_members() map[Member_ID]MemberInfo {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
// Create a copy of the members_list to avoid race conditions
|
||||
current_members := make(map[Member_ID]MemberInfo)
|
||||
for id, info := range m.members_list {
|
||||
current_members[id] = info
|
||||
}
|
||||
return current_members
|
||||
}
|
||||
|
||||
func get_all_member_ids(members map[Member_ID]MemberInfo) []Member_ID {
|
||||
member_ids := make([]Member_ID, 0, len(members) - 1) // Exclude self
|
||||
for id := range members {
|
||||
|
||||
}
|
||||
return member_ids
|
||||
}
|
||||
|
||||
func (m *MembershipService) member_init(ip string, port int) {
|
||||
my_id := Member_ID{ip, port, time.Now()}
|
||||
members_info := MemberInfo{0, "alive", 0, nil, nil}
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.members_list[my_id] = members_info
|
||||
}
|
||||
|
||||
func (m *MembershipService) handle_join(new_member Member_ID, logger *slog.Logger, socket *net.UDPConn) {
|
||||
|
||||
m.mutex.Lock()
|
||||
m.members_list[new_member] = MemberInfo{0, "alive", 0, nil, nil}
|
||||
m.mutex.Unlock()
|
||||
|
||||
//ack payload with teh current membership list
|
||||
gossip_msg := GossipMessage{
|
||||
Type: "gossip",
|
||||
Senders_id: Member_ID{},
|
||||
Members: m.get_current_members(),
|
||||
}
|
||||
data, err := json.Marshal(gossip_msg)
|
||||
if err != nil {
|
||||
logger.Error("Error marshaling gossip message", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = socket.WriteToUDP(data, &net.UDPAddr{IP: net.ParseIP(new_member.ip), Port: new_member.port})
|
||||
if err != nil {
|
||||
logger.Error("Error sending gossip message", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (m *MembershipService) handle_gossip_receive(ctx context.Context, socket *net.UDPConn, logger *slog.Logger, config Config, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
buffer := make([]byte, 65535)
|
||||
//send the join message before processing the gossip messages
|
||||
joined := (config.introducer_ip == config.my_ip.String()) // Introducer node doesn't need to join
|
||||
last_join_time := time.Time{}
|
||||
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !joined && time.Since(last_join_time) >= config.join_retry_interval {
|
||||
// Send join message to introducer
|
||||
join_msg := GossipMessage{
|
||||
Type: "join",
|
||||
Senders_id: Member_ID{ip: config.my_ip.String(), port: config.port, time: time.Now()},
|
||||
Members: map[Member_ID]MemberInfo{},
|
||||
}
|
||||
data, err := json.Marshal(join_msg)
|
||||
if err != nil {
|
||||
logger.Error("Error marshaling join message", "error", err)
|
||||
return
|
||||
}
|
||||
introducer_addr := &net.UDPAddr{
|
||||
IP: net.ParseIP(config.introducer_ip),
|
||||
Port: config.port,
|
||||
}
|
||||
_, err = socket.WriteToUDP(data, introducer_addr)
|
||||
if err != nil {
|
||||
logger.Error("Error sending join message", "error", err)
|
||||
return
|
||||
}
|
||||
last_join_time = time.Now()
|
||||
}
|
||||
|
||||
socket.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, _, err := socket.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
// Timeout occurred, continue to the next iteration to check for context cancellation
|
||||
continue
|
||||
}
|
||||
logger.Error("Error reading from UDP socket", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
var gossip_msg GossipMessage
|
||||
err = json.Unmarshal(buffer[:n], &gossip_msg)
|
||||
if err != nil {
|
||||
logger.Error("Error unmarshaling gossip message", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch gossip_msg.Type {
|
||||
case "gossip":
|
||||
m.handle_gossip(gossip_msg.Members, logger)
|
||||
case "join":
|
||||
if config.introducer_ip == config.my_ip.String() {
|
||||
m.handle_join(gossip_msg.Senders_id, logger, socket)
|
||||
joined = true
|
||||
} else {
|
||||
logger.Error("Received join message but not the introducer")
|
||||
}
|
||||
default:
|
||||
logger.Error("Unknown message type received", "type", gossip_msg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func select_peers(current_members map[Member_ID]MemberInfo, fanout int) []Member_ID {
|
||||
if len(current_members) <= fanout {
|
||||
return get_all_member_ids(current_members)
|
||||
}
|
||||
|
||||
member_ids := get_all_member_ids(current_members)
|
||||
selected := make([]Member_ID, 0, fanout)
|
||||
perm := rand.Perm(len(member_ids))
|
||||
for i := 0; i < fanout; i++ {
|
||||
selected = append(selected, member_ids[perm[i]])
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
|
||||
func (m *MembershipService) handle_gossip_send(ctx context.Context, socket *net.UDPConn, logger *slog.Logger, config Config, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
ticker := time.NewTicker(config.gossip_heartbeat_interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
//increment heart beat for self
|
||||
m.mutex.Lock()
|
||||
for id, info := range m.members_list {
|
||||
if id.ip == config.my_ip.String() && id.port == config.port {
|
||||
info.heart_beat++
|
||||
m.members_list[id] = info
|
||||
break
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
|
||||
current_members := m.get_current_members()
|
||||
selected_peers := select_peers(current_members, config.gossip_fanout)
|
||||
|
||||
for _, peer := range selected_peers {
|
||||
gossip_msg := GossipMessage{
|
||||
Type: "gossip",
|
||||
Members: current_members,
|
||||
}
|
||||
data, err := json.Marshal(gossip_msg)
|
||||
if err != nil {
|
||||
logger.Error("Error marshaling gossip message", "error", err)
|
||||
return
|
||||
}
|
||||
// Send the gossip message to the selected peer
|
||||
peer_addr := &net.UDPAddr{
|
||||
IP: net.ParseIP(peer.ip),
|
||||
Port: peer.port,
|
||||
}
|
||||
_, err = socket.WriteToUDP(data, peer_addr)
|
||||
if err != nil {
|
||||
logger.Error("Error sending gossip message", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MembershipService) handle_failure_detection(ctx context.Context, socket *net.UDPConn, logger *slog.Logger, config Config, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
// TODO: Implement failure detection logic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func start_membership(ctx context.Context, config Config, logger *slog.Logger) {
|
||||
logger.Info("Starting Membership")
|
||||
|
||||
var membership_service MembershipService
|
||||
membership_service.members_list = make(map[Member_ID]MemberInfo)
|
||||
membership_service.member_init(config.my_ip.String(), config.port)
|
||||
|
||||
// 2. Check the error on socket creation
|
||||
socket, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: *config.my_ip,
|
||||
Port: config.port,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Failed to create UDP listener", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. Keep defer socket.Close() since the function remains blocking
|
||||
defer socket.Close()
|
||||
|
||||
logger.Info("Membership Service Listening on", "IP", config.my_ip.String(), "Port", config.port)
|
||||
|
||||
// 6. Goroutine tracking with WaitGroup
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
|
||||
go membership_service.handle_gossip_receive(ctx, socket, logger, config, &wg)
|
||||
go membership_service.handle_gossip_send(ctx, socket, logger, config, &wg)
|
||||
go membership_service.handle_failure_detection(ctx, socket, logger, config, &wg)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
Reference in new issue
Block a user