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 }