github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/identitylookup/disthash/placement_actor.go (about) 1 package disthash 2 3 import ( 4 "github.com/asynkron/protoactor-go/actor" 5 clustering "github.com/asynkron/protoactor-go/cluster" 6 "log/slog" 7 ) 8 9 type GrainMeta struct { 10 ID *clustering.ClusterIdentity 11 PID *actor.PID 12 } 13 14 type placementActor struct { 15 cluster *clustering.Cluster 16 partitionManager *Manager 17 actors map[string]GrainMeta 18 } 19 20 func newPlacementActor(c *clustering.Cluster, pm *Manager) *placementActor { 21 return &placementActor{ 22 cluster: c, 23 partitionManager: pm, 24 actors: map[string]GrainMeta{}, 25 } 26 } 27 28 func (p *placementActor) Receive(ctx actor.Context) { 29 switch msg := ctx.Message().(type) { 30 case *actor.Started: 31 ctx.Logger().Info("Placement actor started") 32 case *actor.Stopping: 33 ctx.Logger().Info("Placement actor stopping") 34 p.onStopping(ctx) 35 case *actor.Stopped: 36 ctx.Logger().Info("Placement actor stopped") 37 case *actor.Terminated: 38 p.onTerminated(msg, ctx) 39 case *clustering.ActivationRequest: 40 p.onActivationRequest(msg, ctx) 41 case *clustering.ClusterTopology: 42 p.onClusterTopology(msg, ctx) 43 default: 44 ctx.Logger().Error("Invalid message", slog.Any("message", msg), slog.Any("sender", ctx.Sender())) 45 } 46 } 47 48 func (p *placementActor) onTerminated(msg *actor.Terminated, ctx actor.Context) { 49 found, key, meta := p.pidToMeta(msg.Who) 50 51 activationTerminated := &clustering.ActivationTerminated{ 52 Pid: msg.Who, 53 ClusterIdentity: meta.ID, 54 } 55 p.partitionManager.cluster.MemberList.BroadcastEvent(activationTerminated, true) 56 57 if found { 58 delete(p.actors, *key) 59 } 60 } 61 62 func (p *placementActor) onStopping(ctx actor.Context) { 63 futures := make(map[string]*actor.Future, len(p.actors)) 64 65 for key, meta := range p.actors { 66 futures[key] = ctx.PoisonFuture(meta.PID) 67 } 68 69 for key, future := range futures { 70 err := future.Wait() 71 if err != nil { 72 ctx.Logger().Error("Failed to poison actor", slog.String("identity", key), slog.Any("error", err)) 73 } 74 } 75 } 76 77 func (p *placementActor) onActivationRequest(msg *clustering.ActivationRequest, ctx actor.Context) { 78 key := msg.ClusterIdentity.AsKey() 79 meta, found := p.actors[key] 80 if found { 81 response := &clustering.ActivationResponse{ 82 Pid: meta.PID, 83 } 84 ctx.Respond(response) 85 return 86 } 87 88 clusterKind := p.cluster.GetClusterKind(msg.ClusterIdentity.Kind) 89 if clusterKind == nil { 90 ctx.Logger().Error("Unknown cluster kind", slog.String("kind", msg.ClusterIdentity.Kind)) 91 92 // TODO: what to do here? 93 ctx.Respond(nil) 94 return 95 } 96 97 props := clustering.WithClusterIdentity(clusterKind.Props, msg.ClusterIdentity) 98 99 pid := ctx.SpawnPrefix(props, msg.ClusterIdentity.Identity) 100 101 p.actors[key] = GrainMeta{ 102 ID: msg.ClusterIdentity, 103 PID: pid, 104 } 105 106 response := &clustering.ActivationResponse{ 107 Pid: pid, 108 } 109 110 ctx.Respond(response) 111 } 112 113 func (p *placementActor) pidToMeta(pid *actor.PID) (bool, *string, *GrainMeta) { 114 for k, v := range p.actors { 115 if v.PID == pid { 116 return true, &k, &v 117 } 118 } 119 return false, nil, nil 120 } 121 122 func (p *placementActor) onClusterTopology(msg *clustering.ClusterTopology, ctx actor.Context) { 123 rdv := clustering.NewRendezvous() 124 rdv.UpdateMembers(msg.Members) 125 myAddress := p.cluster.ActorSystem.Address() 126 for identity, meta := range p.actors { 127 ownerAddress := rdv.GetByIdentity(identity) 128 if ownerAddress == myAddress { 129 130 ctx.Logger().Debug("Actor stays", slog.String("identity", identity), slog.String("owner", ownerAddress), slog.String("me", myAddress)) 131 continue 132 } 133 134 ctx.Logger().Debug("Actor moved", slog.String("identity", identity), slog.String("owner", ownerAddress), slog.String("me", myAddress)) 135 136 ctx.Poison(meta.PID) 137 } 138 }