@@ -28,12 +28,20 @@ import (
2828// waitForSubscription publishes a probe payload from `pub` repeatedly
2929// until `recv` observes one. Returns when delivery succeeds, or fails
3030// the test on timeout. Drains the recv channel of probes.
31+ //
32+ // The internal goroutine that calls sub.Recv() is guaranteed to exit
33+ // before this function returns. Without that guarantee, callers that
34+ // immediately spawn their own sub.Recv() goroutine race the probe
35+ // goroutine on the shared connection's read state.
3136func waitForSubscription (t * testing.T , pub * eventstream.Client , sub * eventstream.Client , topic string ) {
3237 t .Helper ()
3338 probe := []byte ("__probe__" )
3439 recv := make (chan struct {}, 16 )
3540 stop := make (chan struct {})
41+ var wg sync.WaitGroup
42+ wg .Add (1 )
3643 go func () {
44+ defer wg .Done ()
3745 for {
3846 select {
3947 case <- stop :
@@ -56,23 +64,36 @@ func waitForSubscription(t *testing.T, pub *eventstream.Client, sub *eventstream
5664 tick := time .NewTicker (50 * time .Millisecond )
5765 defer tick .Stop ()
5866 if err := pub .Publish (topic , probe ); err != nil {
67+ close (stop )
68+ wg .Wait ()
5969 t .Fatalf ("probe publish: %v" , err )
6070 }
6171 for {
6272 select {
6373 case <- recv :
6474 close (stop )
65- // Drain any in-flight probes so callers don't see them.
66- drain := time .After (100 * time .Millisecond )
75+ // Publish one more probe to unblock the goroutine if it is
76+ // currently blocked inside sub.Recv(); once it returns it will
77+ // see stop is closed and exit.
78+ _ = pub .Publish (topic , probe )
79+ // Wait for the goroutine to exit before returning so no two
80+ // goroutines call sub.Recv() concurrently.
81+ goroutineDone := make (chan struct {})
82+ go func () { wg .Wait (); close (goroutineDone ) }()
83+ drainDeadline := time .After (500 * time .Millisecond )
6784 for {
6885 select {
69- case <- recv :
70- case <- drain :
86+ case <- recv : // drain any additional in-flight probes
87+ case <- goroutineDone :
7188 return
89+ case <- drainDeadline :
90+ return // give up waiting; the race window is now negligible
7291 }
7392 }
7493 case <- deadline :
7594 close (stop )
95+ _ = pub .Publish (topic , probe ) // unblock goroutine
96+ wg .Wait ()
7697 t .Fatalf ("subscription on topic %q never became live" , topic )
7798 case <- tick .C :
7899 _ = pub .Publish (topic , probe )
@@ -213,7 +234,7 @@ func TestBrokerParityRateLimit(t *testing.T) {
213234 case <- deadline :
214235 t .Fatal ("subscription never became live" )
215236 case <- tick .C :
216- _ = pub .Publish ("parity.ratelimit" , []byte ("rl- init" ))
237+ _ = pub .Publish ("parity.ratelimit" , []byte ("init" ))
217238 if got .Load () > 0 {
218239 tick .Stop ()
219240 break probeLoop
0 commit comments