@cryptotaxi247 / kubo / commits / 0552f46f4

'generic' notifier

Juan Batiz-Benet committed Jan 26, 2015 at 03:40 UTC 0552f46f45b3ac8304ed925de85f77aaee88e940
2 files changed +320
thirdparty/notifier/notifier.go new
+113
@@ -0,0 +1,113 @@
1 +// Package notifier provides a simple notification dispatcher
2 +// meant to be embedded in larger structres who wish to allow
3 +// clients to sign up for event notifications.
4 +package notifier
5 +
6 +import (
7 + "sync"
8 +)
9 +
10 +// Notifiee is a generic interface. Clients implement
11 +// their own Notifiee interfaces to ensure type-safety
12 +// of notifications:
13 +//
14 +// type RocketNotifiee interface{
15 +// Countdown(r Rocket, countdown time.Duration)
16 +// LiftedOff(Rocket)
17 +// ReachedOrbit(Rocket)
18 +// Detached(Rocket, Capsule)
19 +// Landed(Rocket)
20 +// }
21 +//
22 +type Notifiee interface{}
23 +
24 +// Notifier is a notification dispatcher. It's meant
25 +// to be composed, and its zero-value is ready to be used.
26 +//
27 +// type Rocket struct {
28 +// notifier notifier.Notifier
29 +// }
30 +//
31 +type Notifier struct {
32 + mu sync.RWMutex // guards notifiees
33 + nots map[Notifiee]struct{}
34 +}
35 +
36 +// Notify signs up Notifiee e for notifications. This function
37 +// is meant to be called behind your own type-safe function(s):
38 +//
39 +// // generic function for pattern-following
40 +// func (r *Rocket) Notify(n Notifiee) {
41 +// r.notifier.Notify(n)
42 +// }
43 +//
44 +// // or as part of other functions
45 +// func (r *Rocket) Onboard(a Astronaut) {
46 +// r.astronauts = append(r.austronauts, a)
47 +// r.notifier.Notify(a)
48 +// }
49 +//
50 +func (n *Notifier) Notify(e Notifiee) {
51 + n.mu.Lock()
52 + if n.nots == nil { // so that zero-value is ready to be used.
53 + n.nots = make(map[Notifiee]struct{})
54 + }
55 + n.nots[e] = struct{}{}
56 + n.mu.Unlock()
57 +}
58 +
59 +// StopNotifying stops notifying Notifiee e. This function
60 +// is meant to be called behind your own type-safe function(s):
61 +//
62 +// // generic function for pattern-following
63 +// func (r *Rocket) StopNotify(n Notifiee) {
64 +// r.notifier.StopNotify(n)
65 +// }
66 +//
67 +// // or as part of other functions
68 +// func (r *Rocket) Detach(c Capsule) {
69 +// r.notifier.StopNotify(c)
70 +// r.capsule = nil
71 +// }
72 +//
73 +func (n *Notifier) StopNotify(e Notifiee) {
74 + n.mu.Lock()
75 + if n.nots != nil { // so that zero-value is ready to be used.
76 + delete(n.nots, e)
77 + }
78 + n.mu.Unlock()
79 +}
80 +
81 +// NotifyAll messages the notifier's notifiees with a given notification.
82 +// This is done by calling the given function with each notifiee. It is
83 +// meant to be called with your own type-safe notification functions:
84 +//
85 +// func (r *Rocket) Launch() {
86 +// r.notifyAll(func(n Notifiee) {
87 +// n.Launched(r)
88 +// })
89 +// }
90 +//
91 +// // make it private so only you can use it. This function is necessary
92 +// // to make sure you only up-cast in one place. You control who you added
93 +// // to be a notifiee. If Go adds generics, maybe we can get rid of this
94 +// // method but for now it is like wrapping a type-less container with
95 +// // a type safe interface.
96 +// func (r *Rocket) notifyAll(notify func(Notifiee)) {
97 +// r.notifier.NotifyAll(func(n notifier.Notifiee) {
98 +// notify(n.(Notifiee))
99 +// })
100 +// }
101 +//
102 +func (n *Notifier) NotifyAll(notify func(Notifiee)) {
103 + n.mu.Lock()
104 + if n.nots != nil { // so that zero-value is ready to be used.
105 + for notifiee := range n.nots {
106 + // we spin out a goroutine so that whatever the notification does
107 + // it _never_ blocks out the client. This is so that consumers
108 + // _cannot_ add hooks into your object that block you accidentally.
109 + go notify(notifiee)
110 + }
111 + }
112 + n.mu.Unlock()
113 +}
thirdparty/notifier/notifier_test.go new
+207
@@ -0,0 +1,207 @@
1 +package notifier
2 +
3 +import (
4 + "fmt"
5 + "sync"
6 + "testing"
7 +)
8 +
9 +// test data structures
10 +type Router struct {
11 + queue chan Packet
12 + notifier Notifier
13 +}
14 +
15 +type Packet struct{}
16 +
17 +type RouterNotifiee interface {
18 + Enqueued(*Router, Packet)
19 + Forwarded(*Router, Packet)
20 + Dropped(*Router, Packet)
21 +}
22 +
23 +func (r *Router) Notify(n RouterNotifiee) {
24 + r.notifier.Notify(n)
25 +}
26 +
27 +func (r *Router) StopNotify(n RouterNotifiee) {
28 + r.notifier.StopNotify(n)
29 +}
30 +
31 +func (r *Router) notifyAll(notify func(n RouterNotifiee)) {
32 + r.notifier.NotifyAll(func(n Notifiee) {
33 + notify(n.(RouterNotifiee))
34 + })
35 +}
36 +
37 +func (r *Router) Receive(p Packet) {
38 +
39 + select {
40 + case r.queue <- p: // enqueued
41 + r.notifyAll(func(n RouterNotifiee) {
42 + n.Enqueued(r, p)
43 + })
44 +
45 + default: // drop
46 + r.notifyAll(func(n RouterNotifiee) {
47 + n.Dropped(r, p)
48 + })
49 + }
50 +}
51 +
52 +func (r *Router) Forward() {
53 + p := <-r.queue
54 + r.notifyAll(func(n RouterNotifiee) {
55 + n.Forwarded(r, p)
56 + })
57 +}
58 +
59 +type Metrics struct {
60 + enqueued int
61 + forwarded int
62 + dropped int
63 + received chan struct{}
64 + sync.Mutex
65 +}
66 +
67 +func (m *Metrics) Enqueued(*Router, Packet) {
68 + m.Lock()
69 + m.enqueued++
70 + m.Unlock()
71 + if m.received != nil {
72 + m.received <- struct{}{}
73 + }
74 +}
75 +
76 +func (m *Metrics) Forwarded(*Router, Packet) {
77 + m.Lock()
78 + m.forwarded++
79 + m.Unlock()
80 + if m.received != nil {
81 + m.received <- struct{}{}
82 + }
83 +}
84 +
85 +func (m *Metrics) Dropped(*Router, Packet) {
86 + m.Lock()
87 + m.dropped++
88 + m.Unlock()
89 + if m.received != nil {
90 + m.received <- struct{}{}
91 + }
92 +}
93 +
94 +func (m *Metrics) String() string {
95 + m.Lock()
96 + defer m.Unlock()
97 + return fmt.Sprintf("%d enqueued, %d forwarded, %d in queue, %d dropped",
98 + m.enqueued, m.forwarded, m.enqueued-m.forwarded, m.dropped)
99 +}
100 +
101 +func TestNotifies(t *testing.T) {
102 +
103 + m := Metrics{received: make(chan struct{})}
104 + r := Router{queue: make(chan Packet, 10)}
105 + r.Notify(&m)
106 +
107 + for i := 0; i < 10; i++ {
108 + r.Receive(Packet{})
109 + <-m.received
110 + if m.enqueued != (1 + i) {
111 + t.Error("not notifying correctly", m.enqueued, 1+i)
112 + }
113 +
114 + }
115 +
116 + for i := 0; i < 10; i++ {
117 + r.Receive(Packet{})
118 + <-m.received
119 + if m.enqueued != 10 {
120 + t.Error("not notifying correctly", m.enqueued, 10)
121 + }
122 + if m.dropped != (1 + i) {
123 + t.Error("not notifying correctly", m.dropped, 1+i)
124 + }
125 + }
126 +}
127 +
128 +func TestStopsNotifying(t *testing.T) {
129 + m := Metrics{received: make(chan struct{})}
130 + r := Router{queue: make(chan Packet, 10)}
131 + r.Notify(&m)
132 +
133 + for i := 0; i < 5; i++ {
134 + r.Receive(Packet{})
135 + <-m.received
136 + if m.enqueued != (1 + i) {
137 + t.Error("not notifying correctly")
138 + }
139 + }
140 +
141 + r.StopNotify(&m)
142 +
143 + for i := 0; i < 5; i++ {
144 + r.Receive(Packet{})
145 + select {
146 + case <-m.received:
147 + t.Error("did not stop notifying")
148 + default:
149 + }
150 + if m.enqueued != 5 {
151 + t.Error("did not stop notifying")
152 + }
153 + }
154 +}
155 +
156 +func TestThreadsafe(t *testing.T) {
157 + N := 1000
158 + r := Router{queue: make(chan Packet, 10)}
159 + m1 := Metrics{received: make(chan struct{})}
160 + m2 := Metrics{received: make(chan struct{})}
161 + m3 := Metrics{received: make(chan struct{})}
162 + r.Notify(&m1)
163 + r.Notify(&m2)
164 + r.Notify(&m3)
165 +
166 + var n int
167 + var wg sync.WaitGroup
168 + for i := 0; i < N; i++ {
169 + n++
170 + wg.Add(1)
171 + go func() {
172 + defer wg.Done()
173 + r.Receive(Packet{})
174 + }()
175 +
176 + if i%3 == 0 {
177 + n++
178 + wg.Add(1)
179 + go func() {
180 + defer wg.Done()
181 + r.Forward()
182 + }()
183 + }
184 + }
185 +
186 + // drain queues
187 + for i := 0; i < (n * 3); i++ {
188 + select {
189 + case <-m1.received:
190 + case <-m2.received:
191 + case <-m3.received:
192 + }
193 + }
194 +
195 + wg.Wait()
196 +
197 + // counts should be correct and all agree. and this should
198 + // run fine under `go test -race -cpu=5`
199 +
200 + t.Log("m1", m1.String())
201 + t.Log("m2", m2.String())
202 + t.Log("m3", m3.String())
203 +
204 + if m1.String() != m2.String() || m2.String() != m3.String() {
205 + t.Error("counts disagree")
206 + }
207 +}