github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/gossip_actor.go (about) 1 // Copyright (C) 2015-2022 Asynkron AB All rights reserved 2 3 package cluster 4 5 import ( 6 "log/slog" 7 "time" 8 9 "github.com/asynkron/gofun/set" 10 "github.com/asynkron/protoactor-go/actor" 11 ) 12 13 // convenience customary type to represent an empty value 14 // that takes no space in memory. 15 type empty struct{} 16 17 // Actor used to send gossip messages around 18 type GossipActor struct { 19 gossipRequestTimeout time.Duration 20 gossip Gossip 21 22 /// Message throttler 23 throttler actor.ShouldThrottle 24 } 25 26 // Creates a new GossipActor and returns a pointer to its location in the heap 27 func NewGossipActor(requestTimeout time.Duration, myID string, getBlockedMembers func() set.Set[string], fanOut int, maxSend int, system *actor.ActorSystem) *GossipActor { 28 29 logger := system.Logger() 30 informer := newInformer(myID, getBlockedMembers, fanOut, maxSend, logger) 31 gossipActor := GossipActor{ 32 gossipRequestTimeout: requestTimeout, 33 gossip: informer, 34 } 35 36 gossipActor.throttler = actor.NewThrottleWithLogger(logger, 3, 60*time.Second, func(logger *slog.Logger, counter int32) { 37 logger.Debug("[Gossip] Sending GossipRequest", slog.Int("throttled", int(counter))) 38 }) 39 40 return &gossipActor 41 } 42 43 // Receive method. 44 func (ga *GossipActor) Receive(ctx actor.Context) { 45 switch r := ctx.Message().(type) { 46 case *actor.Started, *actor.Stopping, *actor.Stopped: 47 // pass 48 case *SetGossipStateKey: 49 ga.onSetGossipStateKey(r, ctx) 50 case *GetGossipStateRequest: 51 ga.onGetGossipStateKey(r, ctx) 52 case *GossipRequest: 53 ga.onGossipRequest(r, ctx) 54 case *SendGossipStateRequest: 55 ga.onSendGossipState(ctx) 56 case *AddConsensusCheck: 57 ga.onAddConsensusCheck(r) 58 case *RemoveConsensusCheck: 59 ga.onRemoveConsensusCheck(r) 60 case *ClusterTopology: 61 ga.onClusterTopology(r) 62 case *GossipResponse: 63 ctx.Logger().Error("GossipResponse should not be received by GossipActor") // it should be a response to a request 64 default: 65 ctx.Logger().Warn("Gossip received unknown message request", slog.Any("message", r)) 66 } 67 } 68 69 func (ga *GossipActor) onClusterTopology(topology *ClusterTopology) { 70 ga.gossip.UpdateClusterTopology(topology) 71 } 72 73 func (ga *GossipActor) onAddConsensusCheck(r *AddConsensusCheck) { 74 ga.gossip.AddConsensusCheck(r.ID, r.Check) 75 } 76 77 func (ga *GossipActor) onRemoveConsensusCheck(r *RemoveConsensusCheck) { 78 ga.gossip.RemoveConsensusCheck(r.ID) 79 } 80 81 func (ga *GossipActor) onGetGossipStateKey(r *GetGossipStateRequest, ctx actor.Context) { 82 state := ga.gossip.GetState(r.Key) 83 res := NewGetGossipStateResponse(state) 84 ctx.Respond(&res) 85 } 86 87 func (ga *GossipActor) onGossipRequest(r *GossipRequest, ctx actor.Context) { 88 if ga.throttler() == actor.Open { 89 ctx.Logger().Debug("OnGossipRequest", slog.Any("sender", ctx.Sender())) 90 } 91 ga.ReceiveState(r.State, ctx) 92 93 if !GetCluster(ctx.ActorSystem()).MemberList.ContainsMemberID(r.MemberId) { 94 ctx.Logger().Warn("Got gossip request from unknown member", slog.String("MemberId", r.MemberId)) 95 96 // nothing to send, do not provide sender or state payload 97 // ctx.Respond(&GossipResponse{State: &GossipState{Members: make(map[string]*GossipState_GossipMemberState)}}) 98 ctx.Respond(&GossipResponse{}) 99 100 return 101 } 102 103 memberState := ga.gossip.GetMemberStateDelta(r.MemberId) 104 if !memberState.HasState { 105 ctx.Logger().Warn("Got gossip request from member, but no state was found", slog.String("MemberId", r.MemberId)) 106 107 // nothing to send, do not provide sender or state payload 108 ctx.Respond(&GossipResponse{}) 109 110 return 111 } 112 113 ctx.Respond(&GossipResponse{}) 114 return 115 116 // turn off acking for now 117 118 //msg := GossipResponse{ 119 // State: memberState.State, 120 //} 121 //future := ctx.RequestFuture(ctx.Sender(), &msg, GetCluster(ctx.ActorSystem()).Config.GossipRequestTimeout) 122 // 123 //ctx.ReenterAfter(future, func(res interface{}, err error) { 124 // if err != nil { 125 // plog.Warn("onGossipRequest failed", log.String("MemberId", r.MemberId), log.Error(err)) 126 // return 127 // } 128 // 129 // if _, ok := res.(*GossipResponseAck); ok { 130 // memberState.CommitOffsets() 131 // return 132 // } 133 // 134 // m, ok := res.(proto.Message) 135 // if !ok { 136 // plog.Warn("onGossipRequest failed", log.String("MemberId", r.MemberId), log.Error(err)) 137 // return 138 // } 139 // n := string(proto.MessageName(m).Name()) 140 // 141 // plog.Error("onGossipRequest received unknown response message", log.String("type", n), log.Message(r)) 142 //}) 143 } 144 145 func (ga *GossipActor) onSetGossipStateKey(r *SetGossipStateKey, ctx actor.Context) { 146 key, message := r.Key, r.Value 147 ga.gossip.SetState(key, message) 148 149 if ctx.Sender() != nil { 150 ctx.Respond(&SetGossipStateResponse{}) 151 } 152 } 153 154 func (ga *GossipActor) onSendGossipState(ctx actor.Context) { 155 ga.gossip.SendState(func(memberState *MemberStateDelta, member *Member) { 156 ga.sendGossipForMember(member, memberState, ctx) 157 }) 158 ctx.Respond(&SendGossipStateResponse{}) 159 } 160 161 func (ga *GossipActor) ReceiveState(remoteState *GossipState, ctx actor.Context) { 162 // stream our updates 163 updates := ga.gossip.ReceiveState(remoteState) 164 for _, update := range updates { 165 ctx.ActorSystem().EventStream.Publish(update) 166 } 167 } 168 169 func (ga *GossipActor) sendGossipForMember(member *Member, memberStateDelta *MemberStateDelta, ctx actor.Context) { 170 pid := actor.NewPID(member.Address(), DefaultGossipActorName) 171 if ga.throttler() == actor.Open { 172 ctx.Logger().Debug("Sending GossipRequest", slog.String("MemberId", member.Id)) 173 } 174 175 // a short timeout is massively important, we cannot afford hanging around waiting 176 // for timeout, blocking other gossips from getting through 177 178 msg := GossipRequest{ 179 MemberId: member.Id, 180 State: memberStateDelta.State, 181 } 182 future := ctx.RequestFuture(pid, &msg, ga.gossipRequestTimeout) 183 184 ctx.ReenterAfter(future, func(res interface{}, err error) { 185 if err != nil { 186 ctx.Logger().Warn("sendGossipForMember failed", slog.String("MemberId", member.Id), slog.Any("error", err)) 187 return 188 } 189 190 resp, ok := res.(*GossipResponse) 191 if !ok { 192 ctx.Logger().Error("sendGossipForMember received unknown response message", slog.Any("message", resp)) 193 194 return 195 } 196 197 memberStateDelta.CommitOffsets() 198 199 if resp.State != nil { 200 ga.ReceiveState(resp.State, ctx) 201 } 202 }) 203 }