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

     1  package remote
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log/slog"
     7  	"time"
     8  
     9  	"github.com/asynkron/protoactor-go/actor"
    10  )
    11  
    12  // Register a known actor props by name
    13  func (r *Remote) Register(kind string, props *actor.Props) {
    14  	r.kinds[kind] = props
    15  }
    16  
    17  // GetKnownKinds returns a slice of known actor "Kinds"
    18  func (r *Remote) GetKnownKinds() []string {
    19  	keys := make([]string, 0, len(r.kinds))
    20  	for k := range r.kinds {
    21  		keys = append(keys, k)
    22  	}
    23  	return keys
    24  }
    25  
    26  type activator struct {
    27  	remote *Remote
    28  }
    29  
    30  // ErrActivatorUnavailable : this error will not panic the Activator.
    31  // It simply tells Partition this Activator is not available
    32  // Partition will then find next available Activator to spawn
    33  var ErrActivatorUnavailable = &ActivatorError{ResponseStatusCodeUNAVAILABLE.ToInt32(), true}
    34  
    35  type ActivatorError struct {
    36  	Code       int32
    37  	DoNotPanic bool
    38  }
    39  
    40  func (e *ActivatorError) Error() string {
    41  	return fmt.Sprint(e.Code)
    42  }
    43  
    44  // ActivatorForAddress returns a PID for the activator at the given address
    45  func (r *Remote) ActivatorForAddress(address string) *actor.PID {
    46  	pid := actor.NewPID(address, "activator")
    47  	return pid
    48  }
    49  
    50  // SpawnFuture spawns a remote actor and returns a Future that completes once the actor is started
    51  func (r *Remote) SpawnFuture(address, name, kind string, timeout time.Duration) *actor.Future {
    52  	activator := r.ActivatorForAddress(address)
    53  	f := r.actorSystem.Root.RequestFuture(activator, &ActorPidRequest{
    54  		Name: name,
    55  		Kind: kind,
    56  	}, timeout)
    57  	return f
    58  }
    59  
    60  // Spawn spawns a remote actor of a given type at a given address
    61  func (r *Remote) Spawn(address, kind string, timeout time.Duration) (*ActorPidResponse, error) {
    62  	return r.SpawnNamed(address, "", kind, timeout)
    63  }
    64  
    65  // SpawnNamed spawns a named remote actor of a given type at a given address
    66  func (r *Remote) SpawnNamed(address, name, kind string, timeout time.Duration) (*ActorPidResponse, error) {
    67  	res, err := r.SpawnFuture(address, name, kind, timeout).Result()
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	switch msg := res.(type) {
    72  	case *ActorPidResponse:
    73  		return msg, nil
    74  	default:
    75  		return nil, errors.New("remote: Unknown response when remote activating")
    76  	}
    77  }
    78  
    79  func newActivatorActor(remote *Remote) actor.Producer {
    80  	return func() actor.Actor {
    81  		return &activator{
    82  			remote: remote,
    83  		}
    84  	}
    85  }
    86  
    87  func (a *activator) Receive(context actor.Context) {
    88  	switch msg := context.Message().(type) {
    89  	case *actor.Started:
    90  		context.Logger().Info("Started Activator")
    91  	case *Ping:
    92  		context.Respond(&Pong{})
    93  	case *ActorPidRequest:
    94  		props, exist := a.remote.kinds[msg.Kind]
    95  
    96  		// if props not exist, return error and panic
    97  		if !exist {
    98  			response := &ActorPidResponse{
    99  				StatusCode: ResponseStatusCodeERROR.ToInt32(),
   100  			}
   101  			context.Respond(response)
   102  			panic(fmt.Errorf("no Props found for kind %s", msg.Kind))
   103  		}
   104  
   105  		name := msg.Name
   106  
   107  		// unnamed actor, assign auto ExtensionID
   108  		if name == "" {
   109  			name = context.ActorSystem().ProcessRegistry.NextId()
   110  		}
   111  
   112  		pid, err := context.SpawnNamed(props, "Remote$"+name)
   113  
   114  		if err == nil {
   115  			response := &ActorPidResponse{Pid: pid}
   116  			context.Respond(response)
   117  		} else if err == actor.ErrNameExists {
   118  			response := &ActorPidResponse{
   119  				Pid:        pid,
   120  				StatusCode: ResponseStatusCodePROCESSNAMEALREADYEXIST.ToInt32(),
   121  			}
   122  			context.Respond(response)
   123  		} else if aErr, ok := err.(*ActivatorError); ok {
   124  			response := &ActorPidResponse{
   125  				StatusCode: aErr.Code,
   126  			}
   127  			context.Respond(response)
   128  			if !aErr.DoNotPanic {
   129  				panic(err)
   130  			}
   131  		} else {
   132  			response := &ActorPidResponse{
   133  				StatusCode: ResponseStatusCodeERROR.ToInt32(),
   134  			}
   135  			context.Respond(response)
   136  			panic(err)
   137  		}
   138  	case actor.SystemMessage, actor.AutoReceiveMessage:
   139  		// ignore
   140  	default:
   141  		context.Logger().Error("Activator received unknown message", slog.Any("message", msg))
   142  	}
   143  }