Actor
An actor in Ergo Framework implements the low-level gen.ProcessBehavior interface. To launch a process based on act.Actor, you need to create an object with an embedded act.Actor and implement a factory function for it. For example:
type MyActor struct {
act.Actor
i int
}
func factoryMyActor() gen.ProcessBehavior {
return &MyActor{}
}act.Actor uses the act.ActorBehavior interface to interact with your object. This interface defines a set of callback methods:
type ActorBehavior interface {
gen.ProcessBehavior
// main callbacks
Init(args ...any) error
HandleMessage(from gen.PID, message any) error
HandleCall(from gen.PID, ref gen.Ref, request any) (any, error)
Terminate(reason error)
// extended callbacks used if SplitHandle was enabled
HandleMessageName(name gen.Atom, from gen.PID, message any) error
HandleMessageAlias(alias gen.Alias, from gen.PID, message any) error
HandleCallName(name gen.Atom, from gen.PID, ref gen.Ref, request any) (any, error)
HandleCallAlias(alias gen.Alias, from gen.PID, ref gen.Ref, request any) (any, error)
// specialized callbacks
HandleInspect() map[string]string
HandleLog(message gen.MessageLog) error
HandleEvent(message gen.MessageEvent) error
}
All methods in the act.ActorBehavior interface are optional, allowing you to implement only the necessary callbacks in your object. Since act.Actor embeds the gen.Process interface, you can directly use its methods from within your actor object.
Example:
func (a *MyActor) Init(args...) error {
// get the gen.Log interface using Log method of embedded gen.Process interface
a.Log().Info("starting process %s", a.PID())
// initialize value
a.i = 100
// sending message to itself with methods Send and PID of embedded gen.Process
a.Send(a.PID(), "hello")
return nil
}
func (a *MyActor) HandleMessage(from gen.PID, message any) error {
a.Log().Info("got message from %s: %s", from, message)
...
// handling message
...
return nil
}
func (a *MyActor) Terminate(reason error) {
a.Log().Info("%s terminated with reason: %s", a.PID(), reason)
}Process Initialization
Process initialization begins when act.Actor invokes the Init method of the act.ActorBehavior interface, passing the args provided during Spawn (or SpawnRegister). If Init completes successfully, the node registers the process, allowing it to receive messages or synchronous requests. If Init fails, Spawn (or SpawnRegister) returns the error. It's important to note that during initialization, the process is not yet registered with the node, limiting access to certain methods of the embedded gen.Process interface until the process is fully initialized.
Handling Asynchronous Messages and Synchronous Requests
The process mailbox contains several queues: Main, System, Urgent, and a special Log queue. In act.Actor, messages are processed in the following order: Urgent, System, Main, and then Log.
Asynchronous messages are sent using the Send method of the gen.Node or gen.Process interface. Upon receiving such a message, act.Actor calls the HandleMessage method of the gen.ActorBehavior interface.
For synchronous requests, the HandleCall callback method is invoked in an actor based on act.Actor. The result returned by this method is sent as the response to the request.
A process can send asynchronous messages to itself, but attempting to make a synchronous request to itself will return the error gen.ErrTimeout..
act.Actor also allows handling synchronous requests asynchronously. This feature lets you delegate the processing of synchronous requests to other processes or delay the response. To do this, the HandleCall method should return nil, and you should use the SendResponse method of the gen.Process interface to send the response. You must provide the gen.PID of the requesting process and the gen.Ref reference of the request.
For debugging or monitoring the internal state, the Inspect method allows high-priority synchronous requests. In act.Actor, this triggers the HandleInspect method. This feature is widely used in the Observer tool.
If the actor process is registered as a logger, the HandleLog method is called when log messages (gen.MessageLog) are received, generated using the gen.Log interface. If the actor process has subscribed to events using the LinkEvent or MonitorEvent methods of gen.Process, the HandleEvent method is called when an event message is received.
Process Termination
To stop a process, simply return a non-nil error value from the message-handling callback. After termination, the Terminate callback method will be called, with the reason argument being the returned error value.
Additionally, you can terminate a process using predefined constants like:
gen.TerminateReasonNormalfor a normal (non-failure) termination.gen.TerminateReasonKillwhen the process is forcibly stopped using theKillmethod from thegen.Nodeinterface.
In Ergo Framework, two additional termination reasons are defined:
gen.TerminateReasonPanic: Occurs when a panic happens during message processing.
gen.TerminateReasonShutdown: This termination reason can be sent by the parent process or the node when the node is stopped using the
StopForcemethod of thegen.Nodeinterface. It is not considered a failure.
At the moment the Terminate callback is invoked, the process has already been removed from the node. Therefore, most methods of the gen.Process interface will return the gen.ErrNotAllowed error.
SplitHandle
The SplitHandle option allows an actor to call the callback methods of the act.ActorBehavior interface based on the process identifier used to send a message or make a synchronous call to your process. You can enable this option using the SetSplitHandle(split bool) method defined in act.Actor. To check the current value of this option, use the SplitHandle method. Here is an example usage:
func (a *MyActor) Init(args...) error {
a.SetSplitHandle(true)
...
return nil
}If your process has a registered associated name and receives a message using that name, act.Actor will call the HandleMessageName callback. The name argument will be the value used to send the message. For synchronous requests, the HandleCallName callback will be invoked.
If a process alias (gen.Alias) was used, the callbacks HandleMessageAlias and HandleCallAlias will be triggered for messages and synchronous requests, respectively. This enables customized handling based on the way the message or request was sent.
TrapExit - handling exit-signal
In Ergo Framework, an exit signal can be sent to your process by another process or node using the SendExit method from the gen.Process or gen.Node interfaces. It can also be generated by the node if your process had a link created via the Link* methods in the gen.Process interface. By default, when an actor receives an exit signal, it terminates and calls the Terminate callback with the reason specified in the exit signal.
To prevent your actor from terminating upon receiving an exit signal, you can enable the option to intercept such signals. This can be done using the SetTrapExit(trap bool) method in act.Actor. The default value is false, but you can enable it at any time, for example, during initialization:
func (a *MyActor) Init(args...) error {
a.SetTrapExit(true)
...
return nil
}When the TrapExit option is enabled, act.Actor converts exit signals into standard gen.MessageExit* messages:
gen.MessageExitPID: The source of the exit signal was a process.
gen.MessageExitProcessID: The source was
gen.ProcessID(a link created usingLinkProcessID).gen.MessageExitAlias: The source was
gen.Alias(link created usingLinkAlias).gen.MessageExitEvent: The source was
gen.Event(link created usingLinkEvent).gen.MessageExitNode: The source was a network connection with a node (link created using
LinkNode).
Last updated
Was this helpful?