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  }