Skip to content

Commit 53a18d2

Browse files
fjlkaralabe
authored andcommitted
event: document select case slice use and add edge case test (ethereum#16680)
Feed keeps active subscription channels in a slice called 'f.sendCases'. The Send method tracks the active cases in a local variable 'cases' whose value is f.sendCases initially. 'cases' shrinks to a shorter prefix of f.sendCases every time a send succeeds, moving the successful case out of range of the active case list. This can be confusing because the two slices share a backing array. Add more comments to document what is going on. Also add a test for removing a case that is in 'f.sentCases' but not 'cases'.
1 parent 7beccb2 commit 53a18d2

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

event/feed.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ func (f *Feed) Send(value interface{}) (nsent int) {
148148
f.sendCases[i].Send = rvalue
149149
}
150150

151-
// Send until all channels except removeSub have been chosen.
151+
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
152+
// of sendCases. When a send succeeds, the corresponding case moves to the end of
153+
// 'cases' and it shrinks by one element.
152154
cases := f.sendCases
153155
for {
154156
// Fast path: try sending without blocking before adding to the select set.
@@ -170,6 +172,7 @@ func (f *Feed) Send(value interface{}) (nsent int) {
170172
index := f.sendCases.find(recv.Interface())
171173
f.sendCases = f.sendCases.delete(index)
172174
if index >= 0 && index < len(cases) {
175+
// Shrink 'cases' too because the removed case was still active.
173176
cases = f.sendCases[:len(cases)-1]
174177
}
175178
} else {

event/feed_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,45 @@ func TestFeedUnsubscribeBlockedPost(t *testing.T) {
235235
wg.Wait()
236236
}
237237

238+
// Checks that unsubscribing a channel during Send works even if that
239+
// channel has already been sent on.
240+
func TestFeedUnsubscribeSentChan(t *testing.T) {
241+
var (
242+
feed Feed
243+
ch1 = make(chan int)
244+
ch2 = make(chan int)
245+
sub1 = feed.Subscribe(ch1)
246+
sub2 = feed.Subscribe(ch2)
247+
wg sync.WaitGroup
248+
)
249+
defer sub2.Unsubscribe()
250+
251+
wg.Add(1)
252+
go func() {
253+
feed.Send(0)
254+
wg.Done()
255+
}()
256+
257+
// Wait for the value on ch1.
258+
<-ch1
259+
// Unsubscribe ch1, removing it from the send cases.
260+
sub1.Unsubscribe()
261+
262+
// Receive ch2, finishing Send.
263+
<-ch2
264+
wg.Wait()
265+
266+
// Send again. This should send to ch2 only, so the wait group will unblock
267+
// as soon as a value is received on ch2.
268+
wg.Add(1)
269+
go func() {
270+
feed.Send(0)
271+
wg.Done()
272+
}()
273+
<-ch2
274+
wg.Wait()
275+
}
276+
238277
func TestFeedUnsubscribeFromInbox(t *testing.T) {
239278
var (
240279
feed Feed

0 commit comments

Comments
 (0)