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 }