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

     1  package actor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log/slog"
     7  	"strings"
     8  
     9  	"github.com/asynkron/protoactor-go/metrics"
    10  	"go.opentelemetry.io/otel/attribute"
    11  	"go.opentelemetry.io/otel/metric"
    12  )
    13  
    14  type deadLetterProcess struct {
    15  	actorSystem *ActorSystem
    16  }
    17  
    18  var _ Process = &deadLetterProcess{}
    19  
    20  func NewDeadLetter(actorSystem *ActorSystem) *deadLetterProcess {
    21  	dp := &deadLetterProcess{
    22  		actorSystem: actorSystem,
    23  	}
    24  
    25  	shouldThrottle := NewThrottle(actorSystem.Config.DeadLetterThrottleCount, actorSystem.Config.DeadLetterThrottleInterval, func(i int32) {
    26  		actorSystem.Logger().Info("[DeadLetter]", slog.Int64("throttled", int64(i)))
    27  	})
    28  
    29  	actorSystem.ProcessRegistry.Add(dp, "deadletter")
    30  	_ = actorSystem.EventStream.Subscribe(func(msg interface{}) {
    31  		if deadLetter, ok := msg.(*DeadLetterEvent); ok {
    32  
    33  			// send back a response instead of timeout.
    34  			if deadLetter.Sender != nil {
    35  				actorSystem.Root.Send(deadLetter.Sender, &DeadLetterResponse{})
    36  			}
    37  
    38  			// bail out if sender is set and deadletter request logging is false
    39  			if !actorSystem.Config.DeadLetterRequestLogging && deadLetter.Sender != nil {
    40  				return
    41  			}
    42  
    43  			if _, isIgnoreDeadLetter := deadLetter.Message.(IgnoreDeadLetterLogging); !isIgnoreDeadLetter {
    44  				if shouldThrottle() == Open {
    45  					actorSystem.Logger().Debug("[DeadLetter]", slog.Any("pid", deadLetter.PID), slog.Any("message", deadLetter.Message), slog.Any("sender", deadLetter.Sender))
    46  				}
    47  			}
    48  		}
    49  	})
    50  
    51  	// this subscriber may not be deactivated.
    52  	// it ensures that Watch commands that reach a stopped actor gets a Terminated message back.
    53  	// This can happen if one actor tries to Watch a PID, while another thread sends a Stop message.
    54  	actorSystem.EventStream.Subscribe(func(msg interface{}) {
    55  		if deadLetter, ok := msg.(*DeadLetterEvent); ok {
    56  			if m, ok := deadLetter.Message.(*Watch); ok {
    57  				// we know that this is a local actor since we get it on our own event stream, thus the address is not terminated
    58  				m.Watcher.sendSystemMessage(actorSystem, &Terminated{
    59  					Who: deadLetter.PID,
    60  					Why: TerminatedReason_NotFound,
    61  				})
    62  			}
    63  		}
    64  	})
    65  
    66  	return dp
    67  }
    68  
    69  // A DeadLetterEvent is published via event.Publish when a message is sent to a nonexistent PID
    70  type DeadLetterEvent struct {
    71  	PID     *PID        // The invalid process, to which the message was sent
    72  	Message interface{} // The message that could not be delivered
    73  	Sender  *PID        // the process that sent the Message
    74  }
    75  
    76  func (dp *deadLetterProcess) SendUserMessage(pid *PID, message interface{}) {
    77  	metricsSystem, ok := dp.actorSystem.Extensions.Get(extensionId).(*Metrics)
    78  	if ok && metricsSystem.enabled {
    79  		ctx := context.Background()
    80  		if instruments := metricsSystem.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
    81  			labels := []attribute.KeyValue{
    82  				attribute.String("address", dp.actorSystem.Address()),
    83  				attribute.String("messagetype", strings.Replace(fmt.Sprintf("%T", message), "*", "", 1)),
    84  			}
    85  
    86  			instruments.DeadLetterCount.Add(ctx, 1, metric.WithAttributes(labels...))
    87  		}
    88  	}
    89  	_, msg, sender := UnwrapEnvelope(message)
    90  	dp.actorSystem.EventStream.Publish(&DeadLetterEvent{
    91  		PID:     pid,
    92  		Message: msg,
    93  		Sender:  sender,
    94  	})
    95  }
    96  
    97  func (dp *deadLetterProcess) SendSystemMessage(pid *PID, message interface{}) {
    98  	dp.actorSystem.EventStream.Publish(&DeadLetterEvent{
    99  		PID:     pid,
   100  		Message: message,
   101  	})
   102  }
   103  
   104  func (dp *deadLetterProcess) Stop(pid *PID) {
   105  	dp.SendSystemMessage(pid, stopMessage)
   106  }