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  }