nix-archive-1(type directoryentry(namesharenode(type directoryentry(namedocnode(type directoryentry(name,go-github-com-arceliar-phony-0.0.0-0.d0c6849node(type directoryentry(nameLICENSEnode(typeregularcontents)MIT License Copyright (c) 2019 Arceliar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ))))))))entry(namesrcnode(type directoryentry(name github.comnode(type directoryentry(nameArceliarnode(type directoryentry(namephonynode(type directoryentry(name .gitignorenode(typeregularcontents# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out ))entry(nameLICENSEnode(typeregularcontents)MIT License Copyright (c) 2019 Arceliar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ))entry(name README.mdnode(typeregularcontents# Phony [![Go Report Card](https://goreportcard.com/badge/github.com/Arceliar/phony)](https://goreportcard.com/report/github.com/Arceliar/phony) [godoc](https://godoc.org/github.com/Arceliar/phony) Phony is a *very* minimal actor model library for Go, inspired by the causal messaging system in the [Pony](https://ponylang.io/) programming language. This was written in a weekend as an exercise/test, to demonstrate how easily the Actor model can be implemented in Go, rather than as something intended for real-world use. Note that these are Actors running in the local process (as in Pony), not in other processes or on other machines (as in [Erlang](https://www.erlang.org/)). Phony was written in response to a few places where, in my opinion, idiomatic Go leaves a lot to be desired: 1. Cyclic networks of goroutines that communicate over channels can deadlock, so you end up needing to either drop messages or write some manual buffering or scheduling logic (which is often error prone). Or you can rewrite your code to have no cycles, but sometimes the problem at hand is best modeled with the cycles. I don't really like any of these options. Go makes concurrency and communication *easy*, but combining them isn't *safe*. 2. Goroutines that wait for work from a channel can leak if not signaled to shut down properly, and that shutdown mechanism needs to be manually implemented in most cases. Sometimes it's as easy as ranging over a channel and defering a close, other times it can be a lot more complicated. It's annoying that Go is garbage collected, but it's killer features (goroutines and channels) still need manual management to avoid leaks. 3. I'm tired of writing infinite for loops over select statements. The code is not reusable and resists composition. Lets say I have some type which normally has a worker goroutine associated with it, sitting in a for loop over a select statement. If I want to embed that type in a new struct, which includes any additional channels that must be selected on, I need to rewrite the entire select loop. There's no mechanism to say "and also add this one behavior" without enumerating the full list of behaviors I want from my worker. This is depressing in light of how nicely things behave when a struct anonymously embeds a type, where fields and functions compose beautifully. ## Features 1. Small implementation, only around 100 lines of code, excluding tests and examples. It depends only on a couple of commonly used standard library packages. 2. `Actor`s are extremely lightweight. On `x86_64`, an actor only takes up 16 bytes for their `Inbox` plus 32 bytes per message. While not running, an `Actor` has no associated goroutines, and it can be garbage collected just like any other object when it is no longer needed, even for cycles of `Actor`s. 3. Asynchronous message passing between `Actor`s. Unlike networks go goroutines communicating over channels, sending messages between `Actor`s cannot deadlock. 4. Unbounded Inbox sizes are kept small in practice through backpressure and scheduling. `Actor`s that send to an overworked recipient will pause at a safe point in the future, and wait until signaled that the recipient has caught up. A paused `Actor` also has no associated goroutine or stack. ## Benchmarks ``` goos: linux goarch: amd64 pkg: github.com/Arceliar/phony BenchmarkBlock-4 1000000 1369 ns/op 160 B/op 3 allocs/op BenchmarkAct-4 5608544 228 ns/op 64 B/op 2 allocs/op BenchmarkActFromNil-4 11455034 97.3 ns/op 32 B/op 1 allocs/op BenchmarkActFromMany-4 5889196 204 ns/op 64 B/op 2 allocs/op BenchmarkActFromManyNil-4 5770095 211 ns/op 64 B/op 2 allocs/op BenchmarkPingPong-4 1766218 652 ns/op 32 B/op 1 allocs/op BenchmarkChannelMany-4 2354626 500 ns/op 0 B/op 0 allocs/op BenchmarkChannel-4 1345572 896 ns/op 0 B/op 0 allocs/op BenchmarkBufferedChannel-4 15126925 70.7 ns/op 0 B/op 0 allocs/op PASS ok github.com/Arceliar/phony 13.738s ``` If you're here then presumably you can read Go, so I'd recommend just checking the code to see exactly what the benchmarks are testing. ))entry(nameactor.gonode(typeregularcontentspackage phony import ( "sync/atomic" "unsafe" ) // A message in the queue type queueElem struct { msg interface{} // func() or func() bool next unsafe.Pointer // *queueElem, accessed atomically count uint8 } // Inbox is an ordered queue of messages which an Actor will process sequentially. // Messages are meant to be in the form of non-blocking functions of 0 arguments, often closures. // The intent is for the Inbox struct to be embedded in other structs to satisfy the Actor interface, where the other fields of the struct are owned by the Actor. // It is up to the user to ensure that memory is used safely, and that messages do not contain blocking operations. // An Inbox must not be copied after first use. type Inbox struct { head *queueElem // Used carefully to avoid needing atomics tail unsafe.Pointer // *queueElem, accessed atomically } // Actor is the interface for Actors, based on their ability to receive a message from another Actor. // It's meant so that structs which embed an Inbox can satisfy a mutually compatible interface for message passing. type Actor interface { Act(Actor, func()) enqueue(interface{}) bool restart() advance() bool } // enqueue puts a message on the Actor's inbox queue and returns the number of messages that have been enqueued since the inbox was last empty. // If the inbox was empty, then the actor was not already running, so enqueue starts it. func (a *Inbox) enqueue(msg interface{}) bool { if msg == nil { panic("tried to send nil message") } q := &queueElem{msg: msg} var tail *queueElem for { q.count = 0 tail = (*queueElem)(atomic.LoadPointer(&a.tail)) if tail != nil { q.count = tail.count + 1 if q.count == 0 { q.count = ^q.count } } if atomic.CompareAndSwapPointer(&a.tail, unsafe.Pointer(tail), unsafe.Pointer(q)) { break } } if tail != nil { //An old tail exists, so update its next pointer to reference q atomic.StorePointer(&tail.next, unsafe.Pointer(q)) } else { // No old tail existed, so no worker is currently running // Update the head to point to q, then start the worker a.head = q a.restart() } return q.count >= backpressureThreshold } // Act adds a message to an Actor's Inbox which tells the Actor to execute the provided function at some point in the future. // When one Actor sends a message to another, the sender is meant to provide itself as the first argument to this function. // If the receiver's Inbox contains too many messages, and the sender argument is non-nil, then the sender is scheduled to pause at a safe point in the future, until the receiver has finished running the action. // A nil first argument is valid, and will prevent any scheduling changes from happening, in cases where an Actor wants to send a message to itself (where this scheduling is just useless overhead) or must receive a message from non-Actor code. func (a *Inbox) Act(from Actor, action func()) { if a.enqueue(action) && from != nil { var s stop a.enqueue(func() { if !s.stop() && from.advance() { from.restart() } }) from.enqueue(s.stop) } } // Block adds a message to an Actor's Inbox which tells the Actor to execute the provided function at some point in the future. // It then blocks until the actor has finished running the provided function. // Block meant exclusively as a convenience function for non-Actor code to send messages and wait for responses. // If an Actor calls Block, then it may cause a deadlock, so Act should always be used instead. func Block(actor Actor, action func()) { done := make(chan struct{}) actor.Act(nil, func() { action(); close(done) }) <-done } // run is executed when a message is placed in an empty Inbox, and launches a worker goroutine. // The worker goroutine processes messages from the Inbox until empty, and then exits. func (a *Inbox) run() { running := true for running { switch msg := a.head.msg.(type) { case func() bool: // used internally by backpressure if msg() { return } case func(): // all external use from Act msg() } running = a.advance() } } // returns true if we still have more work to do func (a *Inbox) advance() bool { head := a.head for { a.head = (*queueElem)(atomic.LoadPointer(&head.next)) if a.head != nil { // Move to the next message return true // more left to do } else if !atomic.CompareAndSwapPointer(&a.tail, unsafe.Pointer(head), nil) { // The head is not the tail, but there was no head.next when we checked // Somebody must be updating it right now, so try again continue } else { // Head and tail are now both nil, our work here is done, exit return false // done processing messages } } } func (a *Inbox) restart() { go a.run() } type stop uint32 func (s *stop) stop() bool { return atomic.SwapUint32((*uint32)(s), 1) == 0 } ))entry(nameactor_mobile.gonode(typeregularcontents//+build mobile package phony // How large a queue can be before backpressure slows down sending to it. const backpressureThreshold = 0 ))entry(nameactor_other.gonode(typeregularcontents//+build !mobile package phony // How large a queue can be before backpressure slows down sending to it. const backpressureThreshold = 255 ))entry(name actor_test.gonode(typeregularcontentsVpackage phony import ( "runtime" "sync" "testing" "unsafe" ) func TestInboxSize(t *testing.T) { var a Inbox var q queueElem t.Logf("Inbox size: %d, message size: %d", unsafe.Sizeof(a), unsafe.Sizeof(q)) } func TestBlock(t *testing.T) { var a Inbox var results []int for idx := 0; idx < 1024; idx++ { n := idx // Because idx gets mutated in place Block(&a, func() { results = append(results, n) }) } for idx, n := range results { if n != idx { t.Errorf("value %d != index %d", n, idx) } } } func TestAct(t *testing.T) { var a Inbox var results []int Block(&a, func() { for idx := 0; idx < 1024; idx++ { n := idx // Because idx gets mutated in place a.Act(&a, func() { results = append(results, n) }) } }) Block(&a, func() {}) for idx, n := range results { if n != idx { t.Errorf("value %d != index %d", n, idx) } } } func BenchmarkBlock(b *testing.B) { var a Inbox for i := 0; i < b.N; i++ { Block(&a, func() {}) } } func BenchmarkAct(b *testing.B) { var a, s Inbox done := make(chan struct{}) idx := 0 var f func() f = func() { if idx < b.N { idx++ a.Act(&s, func() {}) s.Act(nil, f) } else { a.Act(&s, func() { close(done) }) } } s.Act(nil, f) <-done } func BenchmarkActFromNil(b *testing.B) { var a Inbox done := make(chan struct{}) idx := 0 var f func() f = func() { if idx < b.N { idx++ a.Act(nil, f) } else { close(done) } } a.Act(nil, f) <-done } func BenchmarkActFromMany(b *testing.B) { var s Inbox count := runtime.GOMAXPROCS(0) var group sync.WaitGroup for idx := 0; idx < count; idx++ { msgs := b.N / count if idx == 0 { msgs = b.N - (count-1)*msgs } var a Inbox jdx := 0 var f func() f = func() { if jdx < msgs { jdx++ a.Act(&s, func() {}) s.Act(nil, f) } else { a.Act(&s, func() { group.Done() }) } } group.Add(1) a.Act(nil, f) } group.Wait() } func BenchmarkActFromManyNil(b *testing.B) { var s Inbox count := runtime.GOMAXPROCS(0) var group sync.WaitGroup for idx := 0; idx < count; idx++ { msgs := b.N / count if idx == 0 { msgs = b.N - (count-1)*msgs } var a Inbox jdx := 0 var f func() f = func() { if jdx < msgs { jdx++ a.Act(nil, func() {}) s.Act(nil, f) } else { a.Act(nil, func() { group.Done() }) } } group.Add(1) a.Act(nil, f) } group.Wait() } func BenchmarkPingPong(b *testing.B) { var pinger, ponger Inbox done := make(chan struct{}) idx := 0 var ping, pong func() ping = func() { if idx < b.N { idx++ ponger.Act(&pinger, pong) } else { close(done) } } pong = func() { if idx < b.N { idx++ pinger.Act(&ponger, ping) } else { close(done) } } pinger.Act(nil, ping) <-done } func BenchmarkChannelMany(b *testing.B) { done := make(chan struct{}) ch := make(chan func()) go func() { for f := range ch { f() } close(done) }() var group sync.WaitGroup count := runtime.GOMAXPROCS(0) for idx := 0; idx < count; idx++ { msgs := b.N / count if idx == 0 { msgs = b.N - (count-1)*msgs } group.Add(1) go func() { f := func() {} for jdx := 0; jdx < msgs; jdx++ { ch <- f } group.Done() }() } group.Wait() close(ch) <-done } func BenchmarkChannel(b *testing.B) { done := make(chan struct{}) ch := make(chan func()) go func() { for f := range ch { ch <- f } close(done) }() f := func() {} for i := 0; i < b.N; i++ { ch <- f g := <-ch g() } close(ch) <-done } func BenchmarkBufferedChannel(b *testing.B) { ch := make(chan func(), 1) f := func() {} for i := 0; i < b.N; i++ { ch <- f g := <-ch g() } } ))entry(namedoc.gonode(typeregularcontents// Package phony is a small actor model library for Go, inspired by the causal messaging system in the Pony programming language. // An Actor is an interface satisfied by a lightweight Inbox struct. // Structs that embed an Inbox satisfy an interface that allows them to send messages to eachother. // Messages are functions of 0 arguments, typically closures, and should not perform blocking operations. // Message passing is asynchronous, causal, and fast. // Actors implemented by the provided Inbox struct are scheduled to prevent messages queues from growing too large, by pausing at safe breakpoints when an Actor detects that it sent something to another Actor whose inbox is flooded. package phony ))entry(nameexamplesnode(type directoryentry(namecounternode(type directoryentry(namemain.gonode(typeregularcontentspackage main import ( "fmt" "github.com/Arceliar/phony" ) // Structs can embed the Inbox type to fulfill the Actor interface. type printer struct { phony.Inbox } // Functions can be defined to send messages to an Actor from another Actor. func (p *printer) Println(from phony.Actor, msg ...interface{}) { p.Act(from, func() { fmt.Println(msg...) }) } // It's useful to embed an Actor in a struct whose fields the Actor is responsible for. type counter struct { phony.Inbox count int printer *printer } // Act with a nil sender is useful for asking an Actor to do something from non-Actor code. func (c *counter) Increment() { c.Act(nil, func() { c.count++ }) } // Block waits until after a message has been processed before returning. // This can be used to interrogate an Actor from an outside goroutine. // Note that Actors shouldn't use this on eachother, since it blocks, it's just meant for convenience when interfacing with outside code. func (c *counter) Get() int { var n int phony.Block(c, func() { n = c.count }) return n } // Print sends a message to the counter, telling to to call c.printer.Println // Calling Println sends a message to the printer, telling it to print // So message sends become function calls. func (c *counter) Print() { c.Act(nil, func() { c.printer.Println(c, "The count is:", c.count) }) } func main() { c := &counter{printer: new(printer)} // Create an actor for idx := 0; idx < 10; idx++ { c.Increment() // Ask the Actor to do some work c.Print() // And ask it to send a message to another Actor, which handles them asynchronously } n := c.Get() // Inspect the Actor's internal state fmt.Println("Value from Get:", n) // This likely prints before the Print() lines above have finished -- Actors work asynchronously. phony.Block(c.printer, func() {}) // Wait for an Actor to handle a message, in this case just to finish printing fmt.Println("Exiting") } ))))))entry(namego.modnode(typeregularcontents*module github.com/Arceliar/phony go 1.12 )))))))))))