Compare commits
2 Commits
e0571eef88
...
2e8d9e8570
| Author | SHA1 | Date |
|---|---|---|
|
|
2e8d9e8570 | 3 weeks ago |
|
|
ef46fc849a | 3 weeks ago |
15 changed files with 682 additions and 89 deletions
@ -0,0 +1,41 @@ |
|||
package sensor |
|||
|
|||
import "sync" |
|||
|
|||
// Hub broadcasts refresh notifications. Its zero value is ready to use, and all
|
|||
// methods are concurrency-safe. Bursts coalesce so slow subscribers never block
|
|||
// the sender or other subscribers.
|
|||
type Hub struct { |
|||
mu sync.Mutex |
|||
subscribers map[chan struct{}]struct{} |
|||
} |
|||
|
|||
// Subscribe registers a buffered notification channel. The caller must cancel
|
|||
// when finished; cancellation is idempotent and leaves the channel open.
|
|||
func (h *Hub) Subscribe() (<-chan struct{}, func()) { |
|||
events := make(chan struct{}, 1) |
|||
h.mu.Lock() |
|||
if h.subscribers == nil { |
|||
h.subscribers = make(map[chan struct{}]struct{}) |
|||
} |
|||
h.subscribers[events] = struct{}{} |
|||
h.mu.Unlock() |
|||
return events, func() { |
|||
h.mu.Lock() |
|||
delete(h.subscribers, events) |
|||
h.mu.Unlock() |
|||
} |
|||
} |
|||
|
|||
// Notify queues at most one pending notification per subscriber without waiting
|
|||
// for readers. Notifications carry no data; clients fetch the current snapshot.
|
|||
func (h *Hub) Notify() { |
|||
h.mu.Lock() |
|||
defer h.mu.Unlock() |
|||
for events := range h.subscribers { |
|||
select { |
|||
case events <- struct{}{}: |
|||
default: |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
package sensor |
|||
|
|||
import ( |
|||
"sync" |
|||
"testing" |
|||
"time" |
|||
) |
|||
|
|||
func TestHubDeliveryCoalescingAndCancel(t *testing.T) { |
|||
var hub Hub |
|||
slow, cancelSlow := hub.Subscribe() |
|||
fast, cancelFast := hub.Subscribe() |
|||
defer cancelFast() |
|||
done := make(chan struct{}) |
|||
go func() { |
|||
for range 1000 { |
|||
hub.Notify() |
|||
} |
|||
close(done) |
|||
}() |
|||
select { |
|||
case <-done: |
|||
case <-time.After(time.Second): |
|||
t.Fatal("slow subscribers blocked notification") |
|||
} |
|||
for _, events := range []<-chan struct{}{slow, fast} { |
|||
select { |
|||
case <-events: |
|||
default: |
|||
t.Fatal("subscriber missed notification") |
|||
} |
|||
select { |
|||
case <-events: |
|||
t.Fatal("burst was not coalesced") |
|||
default: |
|||
} |
|||
} |
|||
cancelSlow() |
|||
cancelSlow() |
|||
hub.Notify() |
|||
select { |
|||
case _, ok := <-slow: |
|||
if ok { |
|||
t.Fatal("cancelled subscriber received a new event") |
|||
} |
|||
default: |
|||
} |
|||
select { |
|||
case <-fast: |
|||
default: |
|||
t.Fatal("remaining subscriber missed notification") |
|||
} |
|||
} |
|||
|
|||
func TestHubConcurrentUse(t *testing.T) { |
|||
var hub Hub |
|||
hub.Notify() // The zero value also supports notification without subscribers.
|
|||
var workers sync.WaitGroup |
|||
for range 10 { |
|||
workers.Go(func() { |
|||
for range 100 { |
|||
events, cancel := hub.Subscribe() |
|||
hub.Notify() |
|||
select { |
|||
case <-events: |
|||
default: |
|||
t.Error("subscriber missed notification") |
|||
} |
|||
cancel() |
|||
cancel() |
|||
} |
|||
}) |
|||
} |
|||
workers.Wait() |
|||
} |
|||
Loading…
Reference in new issue