├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── eventcast.go └── eventcast_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | tags 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2017 Albert Tedja 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eventcast 2 | 3 | [![Build Status](https://travis-ci.org/atedja/go-eventcast.svg?branch=master)](https://travis-ci.org/atedja/go-eventcast) 4 | 5 | Simple event broadcasting. 6 | 7 | ### Examples 8 | 9 | #### Signaling arbitrary number of workers 10 | 11 | ```go 12 | // spawn workers 13 | for i := 0; i < 100; i++ { 14 | go func() { 15 | closed := eventcast.Listen("we are closed") 16 | for { 17 | select { 18 | case <-closed: 19 | return 20 | default: 21 | // do other things 22 | } 23 | } 24 | }() 25 | } 26 | 27 | // somewhere, sometime later.. 28 | eventcast.Broadcast("we are closed") 29 | ``` 30 | 31 | #### Broadcasting a value to multiple listeners 32 | 33 | ```go 34 | // goroutines waiting for some result or timeout. 35 | for i := 0; i < 10; i++ { 36 | go func() { 37 | select { 38 | case value := <-eventcast.Listen("result"): 39 | // do something with value 40 | case <-time.After(1 * time.Second): 41 | // timeout! 42 | break 43 | } 44 | }() 45 | } 46 | 47 | // some worker 48 | go func() { 49 | // doing something... 50 | 51 | value := "result of processing some data" 52 | eventcast.BroadcastWithValue("result", value) 53 | 54 | // continue doing more 55 | }() 56 | ``` 57 | 58 | #### Racing Your Pigs 59 | 60 | ```go 61 | // go pig! 62 | finished := eventcast.Listen("finished") 63 | for i := 0; i < 10; i++ { 64 | go func(i int) { 65 | <-eventcast.Listen("ready") 66 | <-eventcast.Listen("set") 67 | <-eventcast.Listen("go") 68 | time.Sleep(time.Duration(rand.Intn(5000)) * time.Millisecond) 69 | eventcast.BroadcastWithValue("finished", i) 70 | }(i) 71 | } 72 | 73 | // Allow some time for the pigs to get ready 74 | <-time.After(10 * time.Milisecond) 75 | 76 | eventcast.Broadcast("ready") 77 | <-time.After(1 * time.Second) 78 | 79 | eventcast.Broadcast("set") 80 | <-time.After(1 * time.Second) 81 | 82 | eventcast.Broadcast("go") 83 | winner := <-finished 84 | fmt.Println("Winner is Pig", winner.(int)) 85 | ``` 86 | -------------------------------------------------------------------------------- /eventcast.go: -------------------------------------------------------------------------------- 1 | package eventcast 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var channels = struct { 8 | sync.Mutex 9 | list map[string][]chan interface{} 10 | }{ 11 | list: make(map[string][]chan interface{}), 12 | } 13 | 14 | // Broadcast an event. 15 | // 16 | func Broadcast(event string) { 17 | BroadcastWithValue(event, nil) 18 | } 19 | 20 | // Broadcast an event with a value. 21 | // 22 | func BroadcastWithValue(event string, value interface{}) { 23 | channels.Lock() 24 | defer channels.Unlock() 25 | chans := channels.list[event] 26 | if chans != nil { 27 | for _, ch := range chans { 28 | ch <- value 29 | close(ch) 30 | } 31 | channels.list[event] = nil 32 | } 33 | } 34 | 35 | // Listener. Retrieve a channel that listens to a broadcast event. 36 | // This method creates a one-time use channel, everytime, and a Broadcast() 37 | // will push data and close them. 38 | // 39 | func Listen(event string) chan interface{} { 40 | channels.Lock() 41 | defer channels.Unlock() 42 | out := make(chan interface{}, 1) 43 | if channels.list[event] == nil { 44 | channels.list[event] = make([]chan interface{}, 0, 8) 45 | } 46 | channels.list[event] = append(channels.list[event], out) 47 | return out 48 | } 49 | -------------------------------------------------------------------------------- /eventcast_test.go: -------------------------------------------------------------------------------- 1 | package eventcast 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestNoListener(t *testing.T) { 11 | for i := 0; i < 10; i++ { 12 | Broadcast("hello") 13 | } 14 | } 15 | 16 | func TestSingleListener(t *testing.T) { 17 | go func() { 18 | time.Sleep(100 * time.Millisecond) 19 | Broadcast("hello") 20 | }() 21 | 22 | <-Listen("hello") 23 | } 24 | 25 | func TestMultipleListeners(t *testing.T) { 26 | wg := &sync.WaitGroup{} 27 | wg.Add(100) 28 | for i := 0; i < 100; i++ { 29 | go func() { 30 | <-Listen("hello") 31 | wg.Done() 32 | }() 33 | } 34 | 35 | time.Sleep(100 * time.Millisecond) 36 | Broadcast("hello") 37 | 38 | wg.Wait() 39 | } 40 | 41 | func TestBroadcastWithValue(t *testing.T) { 42 | wg := &sync.WaitGroup{} 43 | wg.Add(100) 44 | for i := 0; i < 100; i++ { 45 | go func() { 46 | data := <-Listen("hello") 47 | assert.Equal(t, "value", data.(string)) 48 | wg.Done() 49 | }() 50 | } 51 | 52 | time.Sleep(100 * time.Millisecond) 53 | BroadcastWithValue("hello", "value") 54 | 55 | wg.Wait() 56 | } 57 | 58 | func TestHeyHoo(t *testing.T) { 59 | wg := &sync.WaitGroup{} 60 | wg.Add(6) 61 | for i := 0; i < 2; i++ { 62 | go func() { 63 | hey := Listen("hey") 64 | hoo := Listen("hoo") 65 | done := Listen("done") 66 | for { 67 | select { 68 | case _, ok := <-hey: 69 | if ok { 70 | wg.Done() 71 | } 72 | case _, ok := <-hoo: 73 | if ok { 74 | wg.Done() 75 | } 76 | case <-done: 77 | wg.Done() 78 | return 79 | } 80 | } 81 | }() 82 | } 83 | 84 | time.Sleep(100 * time.Millisecond) 85 | Broadcast("hey") 86 | Broadcast("hoo") 87 | time.Sleep(100 * time.Millisecond) 88 | Broadcast("done") 89 | wg.Wait() 90 | } 91 | --------------------------------------------------------------------------------