-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from mailgun/thrawn/develop
Added Broadcaster
- Loading branch information
Showing
3 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package holster | ||
|
||
import "sync" | ||
|
||
type Broadcaster interface { | ||
WaitChan(string) chan struct{} | ||
Wait(string) | ||
Broadcast() | ||
Done() | ||
} | ||
|
||
// Broadcasts to goroutines a new event has occurred and any waiting go routines should | ||
// stop waiting and do work. The current implementation is limited to 10,0000 unconsumed | ||
// broadcasts. If the user broadcasts more events than can be consumed calls to broadcast() | ||
// will eventually block until the goroutines can catch up. This ensures goroutines will | ||
// receive at least one event per broadcast() call. | ||
type broadcast struct { | ||
clients map[string]chan struct{} | ||
done chan struct{} | ||
mutex sync.Mutex | ||
} | ||
|
||
func NewBroadcaster() Broadcaster { | ||
return &broadcast{ | ||
clients: make(map[string]chan struct{}), | ||
done: make(chan struct{}), | ||
} | ||
} | ||
|
||
// Notify all Waiting goroutines | ||
func (b *broadcast) Broadcast() { | ||
b.mutex.Lock() | ||
for _, channel := range b.clients { | ||
channel <- struct{}{} | ||
} | ||
b.mutex.Unlock() | ||
} | ||
|
||
// Cancels any Wait() calls that are currently blocked | ||
func (b *broadcast) Done() { | ||
close(b.done) | ||
} | ||
|
||
// Blocks until a broadcast is received | ||
func (b *broadcast) Wait(name string) { | ||
b.mutex.Lock() | ||
channel, ok := b.clients[name] | ||
if !ok { | ||
b.clients[name] = make(chan struct{}, 10000) | ||
channel = b.clients[name] | ||
} | ||
b.mutex.Unlock() | ||
|
||
// Wait for a new event or done is closed | ||
select { | ||
case <-channel: | ||
return | ||
case <-b.done: | ||
return | ||
} | ||
} | ||
|
||
// Returns a channel the caller can use to wait for a broadcast | ||
func (b *broadcast) WaitChan(name string) chan struct{} { | ||
b.mutex.Lock() | ||
channel, ok := b.clients[name] | ||
if !ok { | ||
b.clients[name] = make(chan struct{}, 10000) | ||
channel = b.clients[name] | ||
} | ||
b.mutex.Unlock() | ||
return channel | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package holster_test | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/mailgun/holster" | ||
) | ||
|
||
func TestBroadcast(t *testing.T) { | ||
broadcaster := holster.NewBroadcaster() | ||
ready := make(chan struct{}, 2) | ||
done := make(chan struct{}) | ||
socket := make(chan string, 11) | ||
var mutex sync.Mutex | ||
var chat []string | ||
|
||
// Start some simple chat clients that are responsible for | ||
// sending the contents of the []chat slice to their clients | ||
for i := 0; i < 2; i++ { | ||
go func(idx int) { | ||
var clientIndex int | ||
var once sync.Once | ||
for { | ||
mutex.Lock() | ||
if clientIndex != len(chat) { | ||
// Pretend we are sending a message to our client via a socket | ||
socket <- fmt.Sprintf("Client [%d] Chat: %s\n", idx, chat[clientIndex]) | ||
clientIndex++ | ||
mutex.Unlock() | ||
continue | ||
} | ||
mutex.Unlock() | ||
|
||
// Indicate the client is up and ready to receive broadcasts | ||
once.Do(func() { | ||
ready <- struct{}{} | ||
}) | ||
|
||
// Wait for more chats to be added to chat[] | ||
select { | ||
case <-broadcaster.WaitChan(string(idx)): | ||
case <-done: | ||
return | ||
} | ||
} | ||
}(i) | ||
} | ||
|
||
// Wait for the clients to be ready | ||
<-ready | ||
<-ready | ||
|
||
// Add some chat lines to the []chat slice | ||
for i := 0; i < 5; i++ { | ||
mutex.Lock() | ||
chat = append(chat, fmt.Sprintf("Message '%d'", i)) | ||
mutex.Unlock() | ||
|
||
// Notify any clients there are new chats to read | ||
broadcaster.Broadcast() | ||
} | ||
|
||
var count int | ||
for msg := range socket { | ||
fmt.Printf(msg) | ||
count++ | ||
if count == 10 { | ||
break | ||
} | ||
} | ||
|
||
if count != 10 { | ||
t.Errorf("count != 10") | ||
} | ||
// Tell the clients to quit | ||
close(done) | ||
} |