A tree-based actor framework implementation for Golang.
Inspired by
this is my attempt to leverage Go's simple concurrency and channel-based messaging.
The two primary entities in this package are the Actor
interface and Base
struct. Base
implements Actor
, and needs only be embedded in a user's structs to make them actors. To be active, i.e. to be able to process messages, the actors themselves need to first be 'spawned' either by the main application or by other actors. This spawning process returns an Inbox
, a kind of mailbox for an actor to receive messages. Two public messages are provided in this package:
Message | Description |
---|---|
SendStopMsg(Inbox) |
Requests the actor identified by Inbox to stop. |
SendErrorMsg(Inbox, error) |
Reports an error to the actor identified by Inbox . |
Every spawned actor sequentially processes the received messages in First-In-First-Out order. Therefore, mutexes are not necessary.
The main application spawns an actor via SpawnRoot
function. Such an actor is a Root actor. Any actor can launch another actor via receiver method SpawnNested
. In this case, the former is the creator actor; and the latter is the nested actor.
Built with simplicity in mind, an actor can normally communicate with only its creator and nested actors it creates. The main application always launches the Root
actor, from which the tree proliferates.
graph TD
main[main.go] --> |SpawnRoot| actorA[ActorA]
actorA --> |SpawnNested| actorB[ActorB]
actorA --> |SpawnNested| actorC[ActorC]
actorC --> |SpawnNested| actorD[ActorD]
actorC --> |SpawnNested| actorE[ActorE]
Messages inbound to an actor are generally defined along with the actor. Messages outbound to a creator, i.e. events, are done via interfaces. This leaves the creator actor to handle the events (implement the interfaces) as desired.
Error and stop propogation are two things done very easily with this kind of messaging. By default, an error encountered by a nested actor leads to the termination of the actor, and propogation of the error up the tree until the whole tree stops. This default behavior can be overridden as noted in the Actor API table below.
The snippet below is complete in itself, even though the actor doesn't do anything more than being launched.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
actor "gitlab.com/prithvivishak/goactor"
)
type MyActor struct {
actor.Base
// Other fields here.
}
func main() {
osSignal := make(chan os.Signal)
signal.Notify(osSignal, os.Interrupt, syscall.SIGTERM)
myactor := new(MyActor) // Initialize however you see fit.
rootDone, rootInbox, err := actor.SpawnRoot(myactor, "SomeIDHere")
if err != nil {
fmt.Println("FATAL: Could not launch the root actor.")
os.Exit(1)
}
for {
select {
case <-rootDone:
return
case <-osSignal:
actor.SendStopMsg(rootInbox) // Allow the actor to stop gracefully, which sends above done.
}
}
}
Function | Description |
---|---|
Inbox() Inbox |
Returns the mailbox of the actor. Useful for sending a message to self; e.g. from a spawned go routine or a time.AfterFunc timer. |
CreatorInbox() Inbox |
Returns the mailbox of the actor's creator. Useful for sending messages up the tree hierarchy. |
SpawnNested(Actor, string) (Inbox, error) |
Launches an actor as a nested actor, returning its mailbox for custom use. |
Initialize() error |
Use this function to provide your own initialization steps, including spawning other go routines. |
IsStopping() bool |
Returns true if the actor is in the process of stopping. Use for custom actor termination logic. |
Finalize() |
Use this function to finalize whatever was done in Initialize() . This is guaranteed to run even if Initialize() errors out. |
HandleError(error) error |
Override this function to provide your own error handling. Default is to propagate the error, hence stop. |
HandleLastMsg(Actor, error) error |
Override this function to perform custom actions with the stopped nested actor, including re-spawning it. |
ID() string |
Returns the string used to identify an actor upon launch. Useful in HandleLastMsg and debugging statements. |