github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/cluster.go (about)

     1  package cluster
     2  
     3  import (
     4  	"log/slog"
     5  	"time"
     6  
     7  	"google.golang.org/protobuf/types/known/emptypb"
     8  
     9  	"github.com/asynkron/gofun/set"
    10  
    11  	"github.com/asynkron/protoactor-go/actor"
    12  	"github.com/asynkron/protoactor-go/extensions"
    13  	"github.com/asynkron/protoactor-go/remote"
    14  )
    15  
    16  var extensionID = extensions.NextExtensionID()
    17  
    18  type Cluster struct {
    19  	ActorSystem    *actor.ActorSystem
    20  	Config         *Config
    21  	Gossip         *Gossiper
    22  	PubSub         *PubSub
    23  	Remote         *remote.Remote
    24  	PidCache       *PidCacheValue
    25  	MemberList     *MemberList
    26  	IdentityLookup IdentityLookup
    27  	kinds          map[string]*ActivatedKind
    28  	context        Context
    29  }
    30  
    31  var _ extensions.Extension = &Cluster{}
    32  
    33  func New(actorSystem *actor.ActorSystem, config *Config) *Cluster {
    34  	c := &Cluster{
    35  		ActorSystem: actorSystem,
    36  		Config:      config,
    37  		kinds:       map[string]*ActivatedKind{},
    38  	}
    39  	actorSystem.Extensions.Register(c)
    40  
    41  	c.context = config.ClusterContextProducer(c)
    42  	c.PidCache = NewPidCache()
    43  	c.MemberList = NewMemberList(c)
    44  	c.subscribeToTopologyEvents()
    45  
    46  	actorSystem.Extensions.Register(c)
    47  
    48  	var err error
    49  	c.Gossip, err = newGossiper(c)
    50  	c.PubSub = NewPubSub(c)
    51  
    52  	if err != nil {
    53  		panic(err)
    54  	}
    55  
    56  	return c
    57  }
    58  
    59  func (c *Cluster) subscribeToTopologyEvents() {
    60  	c.ActorSystem.EventStream.Subscribe(func(evt interface{}) {
    61  		if clusterTopology, ok := evt.(*ClusterTopology); ok {
    62  			for _, member := range clusterTopology.Left {
    63  				c.PidCache.RemoveByMember(member)
    64  			}
    65  		}
    66  	})
    67  }
    68  
    69  func (c *Cluster) ExtensionID() extensions.ExtensionID {
    70  	return extensionID
    71  }
    72  
    73  //goland:noinspection GoUnusedExportedFunction
    74  func GetCluster(actorSystem *actor.ActorSystem) *Cluster {
    75  	c := actorSystem.Extensions.Get(extensionID)
    76  
    77  	return c.(*Cluster)
    78  }
    79  
    80  func (c *Cluster) GetBlockedMembers() set.Set[string] {
    81  	return c.Remote.BlockList().BlockedMembers()
    82  }
    83  
    84  func (c *Cluster) StartMember() {
    85  	cfg := c.Config
    86  	c.Remote = remote.NewRemote(c.ActorSystem, c.Config.RemoteConfig)
    87  
    88  	c.initKinds()
    89  
    90  	// TODO: make it possible to become a cluster even if remoting is already started
    91  	c.Remote.Start()
    92  
    93  	address := c.ActorSystem.Address()
    94  	c.Logger().Info("Starting Proto.Actor cluster member", slog.String("address", address))
    95  
    96  	c.IdentityLookup = cfg.IdentityLookup
    97  	c.IdentityLookup.Setup(c, c.GetClusterKinds(), false)
    98  
    99  	// TODO: Disable Gossip for now until API changes are done
   100  	// gossiper must be started whenever any topology events starts flowing
   101  	if err := c.Gossip.StartGossiping(); err != nil {
   102  		panic(err)
   103  	}
   104  	c.PubSub.Start()
   105  	c.MemberList.InitializeTopologyConsensus()
   106  
   107  	if err := cfg.ClusterProvider.StartMember(c); err != nil {
   108  		panic(err)
   109  	}
   110  
   111  	time.Sleep(1 * time.Second)
   112  }
   113  
   114  func (c *Cluster) GetClusterKinds() []string {
   115  	keys := make([]string, 0, len(c.kinds))
   116  	for k := range c.kinds {
   117  		keys = append(keys, k)
   118  	}
   119  
   120  	return keys
   121  }
   122  
   123  func (c *Cluster) StartClient() {
   124  	cfg := c.Config
   125  	c.Remote = remote.NewRemote(c.ActorSystem, c.Config.RemoteConfig)
   126  
   127  	c.Remote.Start()
   128  
   129  	address := c.ActorSystem.Address()
   130  	c.Logger().Info("Starting Proto.Actor cluster-client", slog.String("address", address))
   131  
   132  	c.IdentityLookup = cfg.IdentityLookup
   133  	c.IdentityLookup.Setup(c, c.GetClusterKinds(), true)
   134  
   135  	if err := cfg.ClusterProvider.StartClient(c); err != nil {
   136  		panic(err)
   137  	}
   138  	c.PubSub.Start()
   139  }
   140  
   141  func (c *Cluster) Shutdown(graceful bool) {
   142  	c.Gossip.SetState(GracefullyLeftKey, &emptypb.Empty{})
   143  	c.ActorSystem.Shutdown()
   144  	if graceful {
   145  		_ = c.Config.ClusterProvider.Shutdown(graceful)
   146  		c.IdentityLookup.Shutdown()
   147  		// This is to wait ownership transferring complete.
   148  		time.Sleep(time.Millisecond * 2000)
   149  		c.MemberList.stopMemberList()
   150  		c.IdentityLookup.Shutdown()
   151  		c.Gossip.Shutdown()
   152  	}
   153  
   154  	c.Remote.Shutdown(graceful)
   155  
   156  	address := c.ActorSystem.Address()
   157  	c.Logger().Info("Stopped Proto.Actor cluster", slog.String("address", address))
   158  }
   159  
   160  func (c *Cluster) Get(identity string, kind string) *actor.PID {
   161  	return c.IdentityLookup.Get(NewClusterIdentity(identity, kind))
   162  }
   163  
   164  func (c *Cluster) Request(identity string, kind string, message interface{}, option ...GrainCallOption) (interface{}, error) {
   165  	return c.context.Request(identity, kind, message, option...)
   166  }
   167  
   168  func (c *Cluster) RequestFuture(identity string, kind string, message interface{}, option ...GrainCallOption) (*actor.Future, error) {
   169  	return c.context.RequestFuture(identity, kind, message, option...)
   170  }
   171  
   172  func (c *Cluster) GetClusterKind(kind string) *ActivatedKind {
   173  	k, ok := c.kinds[kind]
   174  	if !ok {
   175  		c.Logger().Error("Invalid kind", slog.String("kind", kind))
   176  
   177  		return nil
   178  	}
   179  
   180  	return k
   181  }
   182  
   183  func (c *Cluster) TryGetClusterKind(kind string) (*ActivatedKind, bool) {
   184  	k, ok := c.kinds[kind]
   185  
   186  	return k, ok
   187  }
   188  
   189  func (c *Cluster) initKinds() {
   190  	for name, kind := range c.Config.Kinds {
   191  		c.kinds[name] = kind.Build(c)
   192  	}
   193  	c.ensureTopicKindRegistered()
   194  }
   195  
   196  // ensureTopicKindRegistered ensures that the topic kind is registered in the cluster
   197  // if topic kind is not registered, it will be registered automatically
   198  func (c *Cluster) ensureTopicKindRegistered() {
   199  	hasTopicKind := false
   200  	for name := range c.kinds {
   201  		if name == TopicActorKind {
   202  			hasTopicKind = true
   203  			break
   204  		}
   205  	}
   206  	if !hasTopicKind {
   207  		store := &EmptyKeyValueStore[*Subscribers]{}
   208  
   209  		c.kinds[TopicActorKind] = NewKind(TopicActorKind, actor.PropsFromProducer(func() actor.Actor {
   210  			return NewTopicActor(store, c.Logger())
   211  		})).Build(c)
   212  	}
   213  }
   214  
   215  func (c *Cluster) Logger() *slog.Logger {
   216  	return c.ActorSystem.Logger()
   217  }