├── Makefile ├── README.md ├── actor.go ├── actor_test.go ├── cache.go ├── message.go └── message_q.go /Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=glam 4 | GOFILES=\ 5 | actor.go\ 6 | 7 | include $(GOROOT)/src/Make.pkg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GLAM 2 | ==== 3 | (GoLang Actor Model) 4 | 5 | **Disclaimer**: This is *all* experimental. I'm in no way, shape, or form an expert on Actor Model Concurrency. 6 | 7 | So Go kind of sucks for concurrently accessing shared data structures. Sure, its primitives are powerful and can lend themselves to a high performance system. For the 99.9% of cases where performance isn't mission-crictical, it could really use a simple way to do the following: 8 | 9 | ```go 10 | // Phonebook shared among many threads. 11 | type Phonebook struct { 12 | book map[string]int 13 | } 14 | 15 | func (b Phonebook) Lookup(name string) (int, bool) { 16 | return book[name] // Thread safety! Yay! 17 | } 18 | 19 | func (b *Phonebook) Add(name string, number int) { 20 | book[name] = number // May I please have a segfault? 21 | } 22 | 23 | // From some webserver, e.g. many concurent goroutines: 24 | func HandleAddRequest(name string, number int) { 25 | book.Add(name, number) 26 | } 27 | ``` 28 | 29 | Turns out that using channels requires an absurd amount of boilerplate. Here's an attempt to reduce that. 30 | 31 | Usage 32 | ===== 33 | 34 | ```go 35 | import "github.com/areusch/glam" 36 | 37 | type Phonebook struct { 38 | glam.Actor 39 | book map[string]int 40 | } 41 | 42 | func (b *Phonebook) Add(name string, number int) { 43 | book[name] = number 44 | } 45 | 46 | func (b *Phonebook) Lookup(name string) (int, bool) { 47 | return book[name] 48 | } 49 | 50 | func (b *Phonebook) LongRunningImportFromFile(reader io.Reader) int { 51 | // Importing a phonebook might take a long time and block on I/O. We can still 52 | // handle this message in the context of the actor, but run it in a deferred 53 | // fashion. Defer() launches a new goroutine and executes DoImport in the new 54 | // routine. When this function returns, no response is sent to the caller. 55 | b.Defer((*B).DoImport, b, reader) 56 | } 57 | 58 | // This function is executed in a new goroutine. If it needs to manipulate any state on 59 | // b, it should invoke a method to do so using b.Call. 60 | func (b *Phonebook) DoImport(r io.Reader, reply glam.Reply) int { 61 | numOk := 0 62 | for entry, err := ReadOneEntry(r); err == nil; entry, err = ReadOneEntry(r) { 63 | // From deferred functions, you can still send messages to the original actor. 64 | if ok := b.Call((*Phonebook).Add, entry["name"], entry["number"])[0].Bool(); ok { 65 | numImported++ 66 | } 67 | } 68 | 69 | return numOk 70 | } 71 | 72 | func main() { 73 | // herp derp 74 | book := Phonebook{glam.Actor{}, make(map[string]int)} 75 | book.StartActor(&book) // Call this before calling "Call" 76 | book.Call((*Phonebook).Lookup, "Jane")[0].Int() 77 | } 78 | ``` 79 | 80 | Performance 81 | =========== 82 | 83 | The tests include a benchmark. On my 2008 MBP: (note these are preliminary, need to look into the channel one) 84 | 85 | ``` 86 | glam_test.BenchmarkActor 500000 5577 ns/op 87 | glam_test.BenchmarkChannel 1000000 1042 ns/op 88 | glam_test.BenchmarkDeferred 200000 8022 ns/op 89 | ``` 90 | 91 | So it's about 5.5x worse for trivial functions. No testing has been done against large numbers of arguments or situations where function calls may block. 92 | -------------------------------------------------------------------------------- /actor.go: -------------------------------------------------------------------------------- 1 | package glam 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime/debug" 7 | ) 8 | 9 | // Internal state needed by the Actor. 10 | type Actor struct { 11 | Q *MessageQueue 12 | Receiver reflect.Value 13 | Deferred bool 14 | Current chan<- Response 15 | } 16 | 17 | const kActorQueueLength int = 1 18 | 19 | // Synchronously invoke function in the actor's own thread, passing args. Returns the 20 | // result of execution. 21 | func (r *Actor) Call(function interface{}, args ...interface{}) []interface{} { 22 | out := make(chan Response, 0) 23 | r.Cast(out, function, args...) 24 | response := <-out 25 | 26 | return response.InterpretAsInterfaces() 27 | } 28 | 29 | // Internal method to verify that the given function can be invoked on the actor's 30 | // receiver with the given args. 31 | func (r *Actor) verifyCallSignature(function interface{}, args []interface{}) { 32 | typ := reflect.TypeOf(function) 33 | if typ.Kind() != reflect.Func { 34 | panic("Function is not a method!") 35 | } 36 | if typ.NumIn() < 1 { 37 | panic("Casted method has no receiver!") 38 | } 39 | if !r.Receiver.Type().AssignableTo(typ.In(0)) { 40 | panic(fmt.Sprintf( 41 | "Cannot assign receiver (of type %s) to %s", r.Receiver.Type(), typ.In(0))) 42 | } 43 | numNonReceiver := typ.NumIn() - 1 44 | if len(args) < numNonReceiver { 45 | panic(fmt.Sprintf( 46 | "Not enough arguments given (needed %d, got %d)", numNonReceiver, len(args))) 47 | } 48 | if len(args) > numNonReceiver && !typ.IsVariadic() { 49 | panic(fmt.Sprintf("Too many args for non-variadic function (needed %d, got %d)", 50 | numNonReceiver, len(args))) 51 | } 52 | for i := 1; i < typ.NumIn(); i++ { 53 | if argType := reflect.TypeOf(args[i-1]); !argType.AssignableTo(typ.In(i)) { 54 | panic( 55 | fmt.Sprintf("Cannot assign arg %d (%s -> %s)", i - 1, argType, typ.In(i))) 56 | } 57 | } 58 | } 59 | 60 | // Asynchronously request that the given function be invoked with the given args. 61 | func (r *Actor) Cast(out chan<- Response, function interface{}, args ...interface{}) { 62 | r.verifyCallSignature(function, args) 63 | r.runInThread(out, r.Receiver, function, args...) 64 | } 65 | 66 | func (r *Actor) runInThread(out chan<- Response, receiver reflect.Value, function interface{}, args ...interface{}) { 67 | if r.Q == nil { 68 | panic("Call StartActor before sending it messages!") 69 | } 70 | 71 | // reflect.Call expects the arguments to be a slice of reflect.Values. We also 72 | // need to ensure that the 0th argument is the receiving struct. 73 | valuedArgs := make([]reflect.Value, len(args)+1) 74 | valuedArgs[0] = receiver 75 | for i, x := range args { 76 | valuedArgs[i+1] = reflect.ValueOf(x) 77 | } 78 | 79 | r.Q.In <- Request{reflect.ValueOf(function), valuedArgs, out} 80 | } 81 | 82 | // Defers responding to a particular call, but gives full control over the response 83 | // to the calling function. Specifically, this function returns a Reply object that 84 | // allows the caller to respond at any given point in the future. If an actor 85 | // invokes this function, it promises to eventually call Send or Panic on the 86 | // reply object. Failing to do this may cause program lockup or panic, since 87 | // goroutines 88 | // 89 | // It is an error to call this function from anything but the message-processing 90 | // goroutine. 91 | func (r *Actor) DeferUnguarded() Reply { 92 | r.Deferred = true 93 | return Reply{Response: r.Current, Replied: false} 94 | } 95 | 96 | // Defers responding to a particular call, and invokes the given function in a 97 | // new goroutine to finish processing the call. The new goroutine invokes the 98 | // function in the same guarded style as the calling context. 99 | // 100 | // It is an error to call this function from anything but the message-processing 101 | // goroutine. 102 | func (r *Actor) Defer(function interface{}, args ...interface{}) { 103 | r.Deferred = true 104 | go r.runDeferred(Reply{Response: r.Current, Replied: false}, function, args...) 105 | } 106 | 107 | func (r *Actor) runDeferred(reply Reply, function interface{}, args ...interface{}) { 108 | valueArgs := make([]reflect.Value, len(args)) 109 | for i := 0; i < len(args); i++ { 110 | valueArgs[i] = reflect.ValueOf(args[i]) 111 | } 112 | reply.Send(guardedExec(reflect.ValueOf(function), valueArgs)) 113 | } 114 | 115 | func guardedExec(function reflect.Value, args []reflect.Value) (response Response) { 116 | defer func() { 117 | if e := recover(); e != nil { 118 | response = ResponseImpl{result: nil, err: e, panicked: true, Stack: debug.Stack(), function: function, args: args} 119 | } 120 | }() 121 | 122 | result := function.Call(args) 123 | response = ResponseImpl{result: result, err: nil, panicked: false} 124 | return 125 | } 126 | 127 | func (r *Actor) processOneRequest(request Request) { 128 | r.Deferred = false 129 | r.Current = request.ReplyTo 130 | response := guardedExec(request.Function, request.Args) 131 | if request.ReplyTo != nil && !r.Deferred { 132 | request.ReplyTo <- response 133 | } 134 | } 135 | 136 | // Start the internal goroutine that powers this actor. Call this function 137 | // before calling Do on this object. 138 | func (r *Actor) StartActor(receiver interface{}) { 139 | r.Q = NewMessageQueue(kActorQueueLength) 140 | r.Receiver = reflect.ValueOf(receiver) 141 | go func() { 142 | for { 143 | request := <-r.Q.Out 144 | r.processOneRequest(request) 145 | } 146 | }() 147 | } 148 | 149 | type Reply struct { 150 | Response chan<- Response 151 | Replied bool 152 | } 153 | 154 | // Indicates that a message has finished processing. Sends a reply to the 155 | // sender indicating this. 156 | func (r *Reply) Send(response Response) { 157 | if r.Replied { 158 | panic("Send/Panic called twice!") 159 | } 160 | 161 | r.Replied = true 162 | 163 | if r.Response != nil { 164 | r.Response <- response 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /actor_test.go: -------------------------------------------------------------------------------- 1 | package glam 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type A struct { 8 | x int 9 | y int 10 | in chan GetXRequest 11 | Actor 12 | } 13 | 14 | type B interface { 15 | Tricks() int 16 | } 17 | 18 | type GetXRequest struct { 19 | x int 20 | out chan GetXResponse 21 | } 22 | 23 | type GetXResponse struct { 24 | x int 25 | err interface{} 26 | } 27 | 28 | func (a A) GoX(x int) int { 29 | out := make(chan GetXResponse) 30 | a.in <- GetXRequest{x, out} 31 | return (<-out).x 32 | } 33 | 34 | func (a A) ProcessGetX() { 35 | for { 36 | request := <-a.in 37 | request.out <- GetXResponse{a.GetX(request.x), nil} 38 | } 39 | } 40 | 41 | func (a A) GetX(x int) int { 42 | return a.x + x 43 | } 44 | 45 | func (a A) DoPanic() int { 46 | panic(a.y) 47 | } 48 | 49 | func (a *A) Tricks() int { 50 | a.Defer((*A).LongTricks, a, a.x) 51 | return a.x 52 | } 53 | 54 | func (a A) LongTricks(x int) int { 55 | return x + 5 56 | } 57 | 58 | func TestGetX(t *testing.T) { 59 | a := A{2, 3, nil, Actor{}} 60 | a.StartActor(a) 61 | 62 | if x := a.Call(A.GetX, 4)[0].(int); x != 6 { 63 | t.Errorf("Expected x = %v, actual %v\n", 6, x) 64 | } 65 | } 66 | 67 | func TestPanic(t *testing.T) { 68 | a := A{2, 3, nil, Actor{}} 69 | a.StartActor(a) 70 | 71 | defer func() { 72 | if e := recover(); e != nil { 73 | if response, ok := e.(Response); ok { 74 | if panicCause, ok2 := response.PanicCause().(int); (!ok2 || panicCause != 3) { 75 | t.Errorf("Expected to receive panic response == 3, actual %v\n", panicCause) 76 | } 77 | } else { 78 | t.Errorf("Expected panic(Response), actual %v\n", e) 79 | } 80 | } 81 | }() 82 | 83 | a.Call(A.DoPanic) 84 | } 85 | 86 | func TestDefer(t *testing.T) { 87 | a := A{3, 4, nil, Actor{}} 88 | a.StartActor(&a) 89 | if val := a.Call((*A).Tricks)[0].(int); val != 8 { 90 | t.Errorf("Expected returning x+5, actual %v\n", val) 91 | } 92 | } 93 | 94 | func BenchmarkActor(b *testing.B) { 95 | b.StopTimer() 96 | a := A{5, 10, nil, Actor{}} 97 | a.StartActor(a) 98 | b.StartTimer() 99 | for i := 0; i < b.N; i++ { 100 | a.Call(A.GetX, 3) 101 | } 102 | } 103 | 104 | func BenchmarkChannel(b *testing.B) { 105 | b.StopTimer() 106 | a := A{5, 10, make(chan GetXRequest), Actor{}} 107 | go a.ProcessGetX() 108 | b.StartTimer() 109 | for i := 0; i < b.N; i++ { 110 | a.GoX(3) 111 | } 112 | } 113 | 114 | func BenchmarkDeferred(b *testing.B) { 115 | b.StopTimer() 116 | a := A{5, 10, nil, Actor{}} 117 | a.StartActor(&a) 118 | b.StartTimer() 119 | for i := 0; i < b.N; i++ { 120 | a.Call((*A).Tricks) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package glam; 2 | 3 | import( 4 | "reflect" 5 | ) 6 | 7 | type RetryBehavior int32 8 | const( 9 | kRetryNever = iota 10 | kRetryAlways 11 | ) 12 | 13 | type Cache struct { 14 | cache map[string]interface{} 15 | pending map[string]*PendingCacheEntry 16 | retryBehavior RetryBehavior 17 | actor *Actor 18 | } 19 | 20 | func NewCache(actor *Actor, retryBehavior RetryBehavior) Cache { 21 | return Cache{retryBehavior: retryBehavior, actor: actor} 22 | } 23 | 24 | func (c *Cache) IsLoaded(key string) bool { 25 | _, ok := c.cache[key] 26 | return ok 27 | } 28 | 29 | func (c *Cache) IsPending(key string) bool { 30 | _, ok := c.pending[key] 31 | return ok 32 | } 33 | 34 | // Retrieves the given key from the cache. 35 | func (c *Cache) Get(key string) (interface{}, Future) { 36 | if value, ok := c.cache[key]; ok { 37 | return value, nil 38 | } else if pending, isPending := c.pending[key]; isPending { 39 | return nil, pending.addWatcher() 40 | } 41 | return nil, nil 42 | } 43 | 44 | func (c *Cache) Load(key string) Promise { 45 | if _, ok := c.pending[key]; ok { 46 | panic("Do not load an already-loaded value") 47 | } 48 | 49 | pending := newPendingCacheEntry(c) 50 | c.pending[key] = pending 51 | return pending 52 | } 53 | 54 | type Promise interface { 55 | Fulfill(x interface{}) 56 | } 57 | 58 | type PendingCacheEntry struct { 59 | key string 60 | watchers []Watcher 61 | cache *Cache 62 | } 63 | 64 | func newPendingCacheEntry(c *Cache) *PendingCacheEntry { 65 | return &PendingCacheEntry{cache: c, watchers: make([]Watcher,0,5)} 66 | } 67 | 68 | func (p *PendingCacheEntry) addWatcher() Future { 69 | watcher := newWatcher() 70 | p.watchers = append(p.watchers, watcher) 71 | return &p.watchers[len(p.watchers) - 1] 72 | } 73 | 74 | func (e *PendingCacheEntry) Fulfill(x interface{}) { 75 | e.cache.actor.runInThread( 76 | nil, reflect.ValueOf(e), (*PendingCacheEntry).notifyListeners, x) 77 | } 78 | 79 | func (e *PendingCacheEntry) notifyListeners(x interface{}) { 80 | delete(e.cache.pending, e.key) 81 | e.cache.cache[e.key] = x 82 | for _, w := range e.watchers { 83 | w.out <- x 84 | } 85 | } 86 | 87 | type Watcher struct { 88 | out chan interface{} 89 | worker interface{} 90 | args []interface{} 91 | } 92 | 93 | type Future interface { 94 | Get() interface{} 95 | } 96 | 97 | func newWatcher() Watcher { 98 | return Watcher{out: make(chan interface{}, 1)} 99 | } 100 | 101 | func (w *Watcher) Get() interface{} { 102 | x := <- w.out 103 | w.out <- x 104 | return x 105 | } 106 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package glam; 2 | 3 | import( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | ) 8 | 9 | // Represents a request to an actor's thread to invoke the given function with 10 | // the given arguments. 11 | type Request struct { 12 | Function reflect.Value 13 | Args []reflect.Value 14 | ReplyTo chan<- Response 15 | } 16 | 17 | type Response interface { 18 | Panicked() bool 19 | PanicCause() interface{} 20 | Interpret() []reflect.Value 21 | InterpretAsInterfaces() []interface{} 22 | } 23 | 24 | // Represents the result of a function invocation. 25 | type ResponseImpl struct { 26 | result []reflect.Value // The return value of the function. 27 | err interface{} // The value passed to panic, if it was called. 28 | panicked bool // True if the invocation called panic. 29 | Stack []byte 30 | function reflect.Value 31 | args []reflect.Value 32 | } 33 | 34 | func (r ResponseImpl) Panicked() bool { 35 | return r.panicked 36 | } 37 | 38 | func (r ResponseImpl) PanicCause() interface{} { 39 | if !r.panicked { 40 | panic("Panic Cause not available") 41 | } 42 | 43 | return r.err 44 | } 45 | 46 | func (r ResponseImpl) PanicStack() { 47 | fmt.Printf("Panic occurred while calling %s(", runtime.FuncForPC(r.function.Pointer()).Name()) 48 | for i, x := range r.args { 49 | if i > 0 { 50 | fmt.Printf(", ") 51 | } 52 | fmt.Printf("%s", x.Type().Name()) 53 | } 54 | fmt.Printf("):\n%s\n", r.Stack) 55 | } 56 | 57 | // If the response indicates that the executor panicked, replicate the panic 58 | // on this thread. Otherwise, return the result. 59 | func (r ResponseImpl) Interpret() []reflect.Value { 60 | if r.panicked { 61 | panic(r) 62 | } 63 | return r.result 64 | } 65 | 66 | func (r ResponseImpl) InterpretAsInterfaces() []interface{} { 67 | values := r.Interpret() 68 | interfaces := make([]interface{}, len(values)) 69 | for i, x := range values { 70 | interfaces[i] = x.Interface() 71 | } 72 | return interfaces 73 | } 74 | -------------------------------------------------------------------------------- /message_q.go: -------------------------------------------------------------------------------- 1 | package glam 2 | 3 | import( 4 | "container/list" 5 | ) 6 | 7 | type MessageQueue struct { 8 | Q *list.List 9 | Limit int 10 | In chan Request 11 | Out chan Request 12 | } 13 | 14 | func NewMessageQueue(limit int) *MessageQueue { 15 | q := new(MessageQueue) 16 | q.Q = list.New() 17 | q.Limit = limit 18 | q.In = make(chan Request) 19 | q.Out = make(chan Request) 20 | go q.Run() 21 | return q 22 | } 23 | 24 | func (q *MessageQueue) processIn(msg Request) bool { 25 | if msg.Function.IsNil() { 26 | q.drain() 27 | close(q.In) 28 | close(q.Out) 29 | return false 30 | } 31 | q.Q.PushBack(msg) 32 | return true 33 | } 34 | 35 | func (q *MessageQueue) doIn() bool { 36 | return q.processIn(<- q.In) 37 | } 38 | 39 | func (q *MessageQueue) doInOut() bool { 40 | select { 41 | case msg := <- q.In: 42 | return q.processIn(msg) 43 | case q.Out <- q.Q.Front().Value.(Request): 44 | q.Q.Remove(q.Q.Front()) 45 | } 46 | return true 47 | } 48 | 49 | func (q *MessageQueue) doOut() { 50 | q.Out <- q.Q.Front().Value.(Request) 51 | q.Q.Remove(q.Q.Front()) 52 | } 53 | 54 | func (q *MessageQueue) Run() { 55 | for { 56 | if q.Q.Len() == 0 { 57 | if !q.doIn() { 58 | return 59 | } 60 | } else if q.Q.Len() < q.Limit { 61 | if !q.doInOut() { 62 | return 63 | } 64 | } else { 65 | q.doOut() 66 | } 67 | } 68 | } 69 | 70 | func (q *MessageQueue) drain() { 71 | for { 72 | select { 73 | case <- q.In: 74 | continue 75 | default: 76 | return 77 | } 78 | } 79 | } 80 | --------------------------------------------------------------------------------