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

     1  package actor
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log/slog"
     8  
     9  	"github.com/asynkron/protoactor-go/metrics"
    10  	"go.opentelemetry.io/otel"
    11  	"go.opentelemetry.io/otel/metric"
    12  )
    13  
    14  type (
    15  	SpawnFunc          func(actorSystem *ActorSystem, id string, props *Props, parentContext SpawnerContext) (*PID, error)
    16  	ReceiverMiddleware func(next ReceiverFunc) ReceiverFunc
    17  	SenderMiddleware   func(next SenderFunc) SenderFunc
    18  	ContextDecorator   func(next ContextDecoratorFunc) ContextDecoratorFunc
    19  	SpawnMiddleware    func(next SpawnFunc) SpawnFunc
    20  )
    21  
    22  // Default values.
    23  var (
    24  	defaultDispatcher      = NewDefaultDispatcher(300)
    25  	defaultMailboxProducer = Unbounded()
    26  	defaultSpawner         = func(actorSystem *ActorSystem, id string, props *Props, parentContext SpawnerContext) (*PID, error) {
    27  		ctx := newActorContext(actorSystem, props, parentContext.Self())
    28  		mb := props.produceMailbox()
    29  
    30  		// prepare the mailbox number counter
    31  		if ctx.actorSystem.Config.MetricsProvider != nil {
    32  			sysMetrics, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
    33  			if ok && sysMetrics.enabled {
    34  				if instruments := sysMetrics.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
    35  					sysMetrics.PrepareMailboxLengthGauge()
    36  					meter := otel.Meter(metrics.LibName)
    37  
    38  					if _, err := meter.RegisterCallback(func(_ context.Context, o metric.Observer) error {
    39  						o.ObserveInt64(instruments.ActorMailboxLength, int64(mb.UserMessageCount()), metric.WithAttributes(sysMetrics.CommonLabels(ctx)...))
    40  						return nil
    41  					}); err != nil {
    42  						err = fmt.Errorf("failed to instrument Actor Mailbox, %w", err)
    43  						actorSystem.Logger().Error(err.Error(), slog.Any("error", err))
    44  					}
    45  				}
    46  			}
    47  		}
    48  
    49  		dp := props.getDispatcher()
    50  		proc := NewActorProcess(mb)
    51  		pid, absent := actorSystem.ProcessRegistry.Add(proc, id)
    52  		if !absent {
    53  			return pid, ErrNameExists
    54  		}
    55  		ctx.self = pid
    56  
    57  		initialize(props, ctx)
    58  
    59  		mb.RegisterHandlers(ctx, dp)
    60  		mb.PostSystemMessage(startedMessage)
    61  		mb.Start()
    62  
    63  		return pid, nil
    64  	}
    65  	defaultContextDecorator = func(ctx Context) Context {
    66  		return ctx
    67  	}
    68  )
    69  
    70  func initialize(props *Props, ctx *actorContext) {
    71  	if props.onInit == nil {
    72  		return
    73  	}
    74  
    75  	for _, init := range props.onInit {
    76  		init(ctx)
    77  	}
    78  }
    79  
    80  // DefaultSpawner this is a hacking way to allow Proto.Router access default spawner func.
    81  var DefaultSpawner SpawnFunc = defaultSpawner
    82  
    83  // ErrNameExists is the error used when an existing name is used for spawning an actor.
    84  var ErrNameExists = errors.New("spawn: name exists")
    85  
    86  // Props represents configuration to define how an actor should be created.
    87  type Props struct {
    88  	spawner                 SpawnFunc
    89  	producer                ProducerWithActorSystem
    90  	mailboxProducer         MailboxProducer
    91  	guardianStrategy        SupervisorStrategy
    92  	supervisionStrategy     SupervisorStrategy
    93  	dispatcher              Dispatcher
    94  	receiverMiddleware      []ReceiverMiddleware
    95  	senderMiddleware        []SenderMiddleware
    96  	spawnMiddleware         []SpawnMiddleware
    97  	receiverMiddlewareChain ReceiverFunc
    98  	senderMiddlewareChain   SenderFunc
    99  	spawnMiddlewareChain    SpawnFunc
   100  	contextDecorator        []ContextDecorator
   101  	contextDecoratorChain   ContextDecoratorFunc
   102  	onInit                  []func(ctx Context)
   103  }
   104  
   105  func (props *Props) getSpawner() SpawnFunc {
   106  	if props.spawner == nil {
   107  		return defaultSpawner
   108  	}
   109  
   110  	return props.spawner
   111  }
   112  
   113  func (props *Props) getDispatcher() Dispatcher {
   114  	if props.dispatcher == nil {
   115  		return defaultDispatcher
   116  	}
   117  
   118  	return props.dispatcher
   119  }
   120  
   121  func (props *Props) getSupervisor() SupervisorStrategy {
   122  	if props.supervisionStrategy == nil {
   123  		return defaultSupervisionStrategy
   124  	}
   125  
   126  	return props.supervisionStrategy
   127  }
   128  
   129  func (props *Props) getContextDecoratorChain() ContextDecoratorFunc {
   130  	if props.contextDecoratorChain == nil {
   131  		return defaultContextDecorator
   132  	}
   133  
   134  	return props.contextDecoratorChain
   135  }
   136  
   137  func (props *Props) produceMailbox() Mailbox {
   138  	if props.mailboxProducer == nil {
   139  		return defaultMailboxProducer()
   140  	}
   141  
   142  	return props.mailboxProducer()
   143  }
   144  
   145  func (props *Props) spawn(actorSystem *ActorSystem, name string, parentContext SpawnerContext) (*PID, error) {
   146  	return props.getSpawner()(actorSystem, name, props, parentContext)
   147  }
   148  
   149  func (props *Props) Configure(opts ...PropsOption) *Props {
   150  	for _, opt := range opts {
   151  		opt(props)
   152  	}
   153  
   154  	return props
   155  }