github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/pubsub_delivery.go (about) 1 package cluster 2 3 import ( 4 "log/slog" 5 "time" 6 7 "github.com/asynkron/protoactor-go/actor" 8 "github.com/asynkron/protoactor-go/remote" 9 ) 10 11 type PubSubMemberDeliveryActor struct { 12 subscriberTimeout time.Duration 13 shouldThrottle actor.ShouldThrottle 14 } 15 16 func NewPubSubMemberDeliveryActor(subscriberTimeout time.Duration, logger *slog.Logger) *PubSubMemberDeliveryActor { 17 return &PubSubMemberDeliveryActor{ 18 subscriberTimeout: subscriberTimeout, 19 shouldThrottle: actor.NewThrottleWithLogger(logger, 10, time.Second, func(logger *slog.Logger, i int32) { 20 logger.Warn("[PubSubMemberDeliveryActor] Throttled logs", slog.Int("count", int(i))) 21 }), 22 } 23 } 24 25 func (p *PubSubMemberDeliveryActor) Receive(c actor.Context) { 26 if batch, ok := c.Message().(*DeliverBatchRequest); ok { 27 topicBatch := &PubSubAutoRespondBatch{Envelopes: batch.PubSubBatch.Envelopes} 28 siList := batch.Subscribers.Subscribers 29 30 invalidDeliveries := make([]*SubscriberDeliveryReport, 0, len(siList)) 31 32 type futureWithIdentity struct { 33 future *actor.Future 34 identity *SubscriberIdentity 35 } 36 futureList := make([]futureWithIdentity, 0, len(siList)) 37 for _, identity := range siList { 38 f := p.DeliverBatch(c, topicBatch, identity) 39 if f != nil { 40 futureList = append(futureList, futureWithIdentity{future: f, identity: identity}) 41 } 42 } 43 44 for _, fWithIdentity := range futureList { 45 _, err := fWithIdentity.future.Result() 46 identityLog := func(err error) { 47 if p.shouldThrottle() == actor.Open { 48 if fWithIdentity.identity.GetPid() != nil { 49 c.Logger().Error("Pub-sub message failed to deliver to PID", slog.String("pid", fWithIdentity.identity.GetPid().String()), slog.Any("error", err)) 50 } else if fWithIdentity.identity.GetClusterIdentity() != nil { 51 c.Logger().Error("Pub-sub message failed to deliver to cluster identity", slog.String("cluster identity", fWithIdentity.identity.GetClusterIdentity().String()), slog.Any("error", err)) 52 } 53 } 54 } 55 56 status := DeliveryStatus_Delivered 57 if err != nil { 58 switch err { 59 case actor.ErrTimeout, remote.ErrTimeout: 60 identityLog(err) 61 status = DeliveryStatus_Timeout 62 case actor.ErrDeadLetter, remote.ErrDeadLetter: 63 identityLog(err) 64 status = DeliveryStatus_SubscriberNoLongerReachable 65 default: 66 identityLog(err) 67 status = DeliveryStatus_OtherError 68 } 69 } 70 if status != DeliveryStatus_Delivered { 71 invalidDeliveries = append(invalidDeliveries, &SubscriberDeliveryReport{Status: status, Subscriber: fWithIdentity.identity}) 72 } 73 } 74 75 if len(invalidDeliveries) > 0 { 76 cluster := GetCluster(c.ActorSystem()) 77 // we use cluster.Call to locate the topic actor in the cluster 78 _, _ = cluster.Request(batch.Topic, TopicActorKind, &NotifyAboutFailingSubscribersRequest{InvalidDeliveries: invalidDeliveries}) 79 } 80 } 81 } 82 83 // DeliverBatch delivers PubSubAutoRespondBatch to SubscriberIdentity. 84 func (p *PubSubMemberDeliveryActor) DeliverBatch(c actor.Context, batch *PubSubAutoRespondBatch, s *SubscriberIdentity) *actor.Future { 85 if pid := s.GetPid(); pid != nil { 86 return p.DeliverToPid(c, batch, pid) 87 } 88 if ci := s.GetClusterIdentity(); ci != nil { 89 return p.DeliverToClusterIdentity(c, batch, ci) 90 } 91 return nil 92 } 93 94 // DeliverToPid delivers PubSubAutoRespondBatch to PID. 95 func (p *PubSubMemberDeliveryActor) DeliverToPid(c actor.Context, batch *PubSubAutoRespondBatch, pid *actor.PID) *actor.Future { 96 return c.RequestFuture(pid, batch, p.subscriberTimeout) 97 } 98 99 // DeliverToClusterIdentity delivers PubSubAutoRespondBatch to ClusterIdentity. 100 func (p *PubSubMemberDeliveryActor) DeliverToClusterIdentity(c actor.Context, batch *PubSubAutoRespondBatch, ci *ClusterIdentity) *actor.Future { 101 cluster := GetCluster(c.ActorSystem()) 102 // deliver to virtual actor 103 // delivery should always be possible, since a virtual actor always exists 104 pid := cluster.Get(ci.Identity, ci.Kind) 105 return c.RequestFuture(pid, batch, p.subscriberTimeout) 106 }