├── .gitignore ├── go.mod ├── README.markdown ├── .github └── workflows │ └── go.yml ├── example_test.go ├── LICENSE ├── mux_observer_test.go ├── broadcaster_test.go ├── broadcaster.go └── mux_observer.go /.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *~ 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dustin/go-broadcast 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | pubsubbing channels. 2 | 3 | This project primarily exists because I've been copying and pasting 4 | the exact same two files into numerous projects. It does work well, 5 | though. 6 | 7 | See [the documentation][doc] for usage and examples. 8 | 9 | 10 | [doc]: https://godoc.org/github.com/dustin/go-broadcast 11 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.16 18 | 19 | - name: Build 20 | run: go build -v ./... 21 | 22 | - name: Test 23 | run: go test -v ./... 24 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | import "log" 4 | 5 | // Example of a simple broadcaster sending numbers to two workers. 6 | // 7 | // Five messages are sent. The first worker prints all five. The second worker prints the first and then unsubscribes. 8 | func Example() { 9 | b := NewBroadcaster(100) 10 | 11 | workerOne(b) 12 | workerTwo(b) 13 | 14 | for i := 0; i < 5; i++ { 15 | log.Printf("Sending %v", i) 16 | b.Submit(i) 17 | } 18 | b.Close() 19 | } 20 | 21 | func workerOne(b Broadcaster) { 22 | ch := make(chan interface{}) 23 | b.Register(ch) 24 | defer b.Unregister(ch) 25 | 26 | // Dump out each message sent to the broadcaster. 27 | go func() { 28 | for v := range ch { 29 | log.Printf("workerOne read %v", v) 30 | } 31 | }() 32 | } 33 | 34 | func workerTwo(b Broadcaster) { 35 | ch := make(chan interface{}) 36 | b.Register(ch) 37 | defer b.Unregister(ch) 38 | defer log.Printf("workerTwo is done\n") 39 | 40 | go func() { 41 | log.Printf("workerTwo read %v\n", <-ch) 42 | }() 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dustin Sallings 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /mux_observer_test.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestMuxBroadcast(t *testing.T) { 9 | wg := sync.WaitGroup{} 10 | 11 | mo := NewMuxObserver(0, 0) 12 | defer mo.Close() 13 | 14 | b1 := mo.Sub() 15 | defer b1.Close() 16 | 17 | b2 := mo.Sub() 18 | defer b2.Close() 19 | 20 | for i := 0; i < 5; i++ { 21 | wg.Add(2) 22 | 23 | cch1 := make(chan interface{}) 24 | b1.Register(cch1) 25 | cch2 := make(chan interface{}) 26 | b2.Register(cch2) 27 | 28 | go func() { 29 | defer wg.Done() 30 | defer b1.Unregister(cch1) 31 | <-cch1 32 | }() 33 | go func() { 34 | defer wg.Done() 35 | defer b2.Unregister(cch2) 36 | <-cch2 37 | }() 38 | 39 | } 40 | 41 | go b1.Submit(1) 42 | go b2.Submit(1) 43 | 44 | wg.Wait() 45 | } 46 | 47 | func TestMuxBroadcastCleanup(t *testing.T) { 48 | mo := NewMuxObserver(0, 0) 49 | b := mo.Sub() 50 | b.Register(make(chan interface{})) 51 | b.Close() 52 | mo.Close() 53 | } 54 | 55 | func BenchmarkMuxBrodcast(b *testing.B) { 56 | chout := make(chan interface{}) 57 | 58 | mo := NewMuxObserver(0, 0) 59 | defer mo.Close() 60 | bc := mo.Sub() 61 | bc.Register(chout) 62 | 63 | for i := 0; i < b.N; i++ { 64 | bc.Submit(nil) 65 | <-chout 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /broadcaster_test.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestBroadcast(t *testing.T) { 9 | wg := sync.WaitGroup{} 10 | 11 | b := NewBroadcaster(100) 12 | defer b.Close() 13 | 14 | for i := 0; i < 5; i++ { 15 | wg.Add(1) 16 | 17 | cch := make(chan interface{}) 18 | 19 | b.Register(cch) 20 | 21 | go func() { 22 | defer wg.Done() 23 | defer b.Unregister(cch) 24 | <-cch 25 | }() 26 | 27 | } 28 | 29 | b.Submit(1) 30 | 31 | wg.Wait() 32 | } 33 | 34 | func TestBroadcastTrySubmit(t *testing.T) { 35 | b := NewBroadcaster(1) 36 | defer b.Close() 37 | 38 | if ok := b.TrySubmit(0); !ok { 39 | t.Fatalf("1st TrySubmit assert error expect=true actual=%v", ok) 40 | } 41 | 42 | if ok := b.TrySubmit(1); ok { 43 | t.Fatalf("2nd TrySubmit assert error expect=false actual=%v", ok) 44 | } 45 | 46 | cch := make(chan interface{}) 47 | b.Register(cch) 48 | 49 | if ok := b.TrySubmit(1); !ok { 50 | t.Fatalf("3rd TrySubmit assert error expect=true actual=%v", ok) 51 | } 52 | } 53 | 54 | func TestBroadcastCleanup(t *testing.T) { 55 | b := NewBroadcaster(100) 56 | b.Register(make(chan interface{})) 57 | b.Close() 58 | } 59 | 60 | func echoer(chin, chout chan interface{}) { 61 | for m := range chin { 62 | chout <- m 63 | } 64 | } 65 | 66 | func BenchmarkDirectSend(b *testing.B) { 67 | chout := make(chan interface{}) 68 | chin := make(chan interface{}) 69 | defer close(chin) 70 | 71 | go echoer(chin, chout) 72 | 73 | for i := 0; i < b.N; i++ { 74 | chin <- nil 75 | <-chout 76 | } 77 | } 78 | 79 | func BenchmarkBrodcast(b *testing.B) { 80 | chout := make(chan interface{}) 81 | 82 | bc := NewBroadcaster(0) 83 | defer bc.Close() 84 | bc.Register(chout) 85 | 86 | for i := 0; i < b.N; i++ { 87 | bc.Submit(nil) 88 | <-chout 89 | } 90 | } 91 | 92 | func BenchmarkParallelDirectSend(b *testing.B) { 93 | chout := make(chan interface{}) 94 | chin := make(chan interface{}) 95 | defer close(chin) 96 | 97 | go echoer(chin, chout) 98 | 99 | b.RunParallel(func(pb *testing.PB) { 100 | for pb.Next() { 101 | chin <- nil 102 | <-chout 103 | } 104 | }) 105 | } 106 | 107 | func BenchmarkParallelBrodcast(b *testing.B) { 108 | chout := make(chan interface{}) 109 | 110 | bc := NewBroadcaster(0) 111 | defer bc.Close() 112 | bc.Register(chout) 113 | 114 | b.RunParallel(func(pb *testing.PB) { 115 | for pb.Next() { 116 | bc.Submit(nil) 117 | <-chout 118 | } 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /broadcaster.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package broadcast provides pubsub of messages over channels. 3 | 4 | A provider has a Broadcaster into which it Submits messages and into 5 | which subscribers Register to pick up those messages. 6 | 7 | */ 8 | package broadcast 9 | 10 | type broadcaster struct { 11 | input chan interface{} 12 | reg chan chan<- interface{} 13 | unreg chan chan<- interface{} 14 | 15 | outputs map[chan<- interface{}]bool 16 | } 17 | 18 | // The Broadcaster interface describes the main entry points to 19 | // broadcasters. 20 | type Broadcaster interface { 21 | // Register a new channel to receive broadcasts 22 | Register(chan<- interface{}) 23 | // Unregister a channel so that it no longer receives broadcasts. 24 | Unregister(chan<- interface{}) 25 | // Shut this broadcaster down. 26 | Close() error 27 | // Submit a new object to all subscribers 28 | Submit(interface{}) 29 | // Try Submit a new object to all subscribers return false if input chan is fill 30 | TrySubmit(interface{}) bool 31 | } 32 | 33 | func (b *broadcaster) broadcast(m interface{}) { 34 | for ch := range b.outputs { 35 | ch <- m 36 | } 37 | } 38 | 39 | func (b *broadcaster) run() { 40 | for { 41 | select { 42 | case m := <-b.input: 43 | b.broadcast(m) 44 | case ch, ok := <-b.reg: 45 | if ok { 46 | b.outputs[ch] = true 47 | } else { 48 | return 49 | } 50 | case ch := <-b.unreg: 51 | delete(b.outputs, ch) 52 | } 53 | } 54 | } 55 | 56 | // NewBroadcaster creates a new broadcaster with the given input 57 | // channel buffer length. 58 | func NewBroadcaster(buflen int) Broadcaster { 59 | b := &broadcaster{ 60 | input: make(chan interface{}, buflen), 61 | reg: make(chan chan<- interface{}), 62 | unreg: make(chan chan<- interface{}), 63 | outputs: make(map[chan<- interface{}]bool), 64 | } 65 | 66 | go b.run() 67 | 68 | return b 69 | } 70 | 71 | func (b *broadcaster) Register(newch chan<- interface{}) { 72 | b.reg <- newch 73 | } 74 | 75 | func (b *broadcaster) Unregister(newch chan<- interface{}) { 76 | b.unreg <- newch 77 | } 78 | 79 | func (b *broadcaster) Close() error { 80 | close(b.reg) 81 | close(b.unreg) 82 | return nil 83 | } 84 | 85 | // Submit an item to be broadcast to all listeners. 86 | func (b *broadcaster) Submit(m interface{}) { 87 | if b != nil { 88 | b.input <- m 89 | } 90 | } 91 | 92 | // TrySubmit attempts to submit an item to be broadcast, returning 93 | // true iff it the item was broadcast, else false. 94 | func (b *broadcaster) TrySubmit(m interface{}) bool { 95 | if b == nil { 96 | return false 97 | } 98 | select { 99 | case b.input <- m: 100 | return true 101 | default: 102 | return false 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /mux_observer.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | type taggedObservation struct { 4 | sub *subObserver 5 | ob interface{} 6 | } 7 | 8 | const ( 9 | register = iota 10 | unregister 11 | purge 12 | ) 13 | 14 | type taggedRegReq struct { 15 | sub *subObserver 16 | ch chan<- interface{} 17 | regType int 18 | } 19 | 20 | // A MuxObserver multiplexes several streams of observations onto a 21 | // single delivery goroutine. 22 | type MuxObserver struct { 23 | subs map[*subObserver]map[chan<- interface{}]bool 24 | reg chan taggedRegReq 25 | input chan taggedObservation 26 | } 27 | 28 | // NewMuxObserver constructs a new MuxObserver. 29 | // 30 | // qlen is the size of the channel buffer for observations sent into 31 | // the mux observer and reglen is the size of the channel buffer for 32 | // registration/unregistration events. 33 | func NewMuxObserver(qlen, reglen int) *MuxObserver { 34 | rv := &MuxObserver{ 35 | subs: map[*subObserver]map[chan<- interface{}]bool{}, 36 | reg: make(chan taggedRegReq, reglen), 37 | input: make(chan taggedObservation, qlen), 38 | } 39 | go rv.run() 40 | return rv 41 | } 42 | 43 | // Close shuts down this mux observer. 44 | func (m *MuxObserver) Close() error { 45 | close(m.reg) 46 | return nil 47 | } 48 | 49 | func (m *MuxObserver) broadcast(to taggedObservation) { 50 | for ch := range m.subs[to.sub] { 51 | ch <- to.ob 52 | } 53 | } 54 | 55 | func (m *MuxObserver) doReg(tr taggedRegReq) { 56 | mm, exists := m.subs[tr.sub] 57 | if !exists { 58 | mm = map[chan<- interface{}]bool{} 59 | m.subs[tr.sub] = mm 60 | } 61 | mm[tr.ch] = true 62 | } 63 | 64 | func (m *MuxObserver) doUnreg(tr taggedRegReq) { 65 | mm, exists := m.subs[tr.sub] 66 | if exists { 67 | delete(mm, tr.ch) 68 | if len(mm) == 0 { 69 | delete(m.subs, tr.sub) 70 | } 71 | } 72 | } 73 | 74 | func (m *MuxObserver) handleReg(tr taggedRegReq) { 75 | switch tr.regType { 76 | case register: 77 | m.doReg(tr) 78 | case unregister: 79 | m.doUnreg(tr) 80 | case purge: 81 | delete(m.subs, tr.sub) 82 | } 83 | } 84 | 85 | func (m *MuxObserver) run() { 86 | for { 87 | select { 88 | case tr, ok := <-m.reg: 89 | if ok { 90 | m.handleReg(tr) 91 | } else { 92 | return 93 | } 94 | default: 95 | select { 96 | case to := <-m.input: 97 | m.broadcast(to) 98 | case tr, ok := <-m.reg: 99 | if ok { 100 | m.handleReg(tr) 101 | } else { 102 | return 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | // Sub creates a new sub-broadcaster from this MuxObserver. 110 | func (m *MuxObserver) Sub() Broadcaster { 111 | return &subObserver{m} 112 | } 113 | 114 | type subObserver struct { 115 | mo *MuxObserver 116 | } 117 | 118 | func (s *subObserver) Register(ch chan<- interface{}) { 119 | s.mo.reg <- taggedRegReq{s, ch, register} 120 | } 121 | 122 | func (s *subObserver) Unregister(ch chan<- interface{}) { 123 | s.mo.reg <- taggedRegReq{s, ch, unregister} 124 | } 125 | 126 | func (s *subObserver) Close() error { 127 | s.mo.reg <- taggedRegReq{s, nil, purge} 128 | return nil 129 | } 130 | 131 | func (s *subObserver) Submit(ob interface{}) { 132 | s.mo.input <- taggedObservation{s, ob} 133 | } 134 | 135 | func (s *subObserver) TrySubmit(ob interface{}) bool { 136 | if s == nil { 137 | return false 138 | } 139 | select { 140 | case s.mo.input <- taggedObservation{s, ob}: 141 | return true 142 | default: 143 | return false 144 | } 145 | } 146 | --------------------------------------------------------------------------------