@@ -2,6 +2,7 @@ package fn
22
33import (
44 "context"
5+ "sync"
56 "testing"
67 "time"
78
@@ -19,7 +20,7 @@ func TestGoroutineManager(t *testing.T) {
1920
2021 taskChan := make (chan struct {})
2122
22- require .NoError (t , m .Go (func (ctx context.Context ) {
23+ require .True (t , m .Go (func (ctx context.Context ) {
2324 <- taskChan
2425 }))
2526
@@ -37,7 +38,7 @@ func TestGoroutineManager(t *testing.T) {
3738 require .Greater (t , stopDelay , time .Second )
3839
3940 // Make sure new goroutines do not start after Stop.
40- require .ErrorIs (t , m .Go (func (ctx context.Context ) {}), ErrStopping )
41+ require .False (t , m .Go (func (ctx context.Context ) {}))
4142
4243 // When Stop() is called, the internal context expires and m.Done() is
4344 // closed. Test this.
@@ -56,7 +57,7 @@ func TestGoroutineManagerContextExpires(t *testing.T) {
5657
5758 m := NewGoroutineManager (ctx )
5859
59- require .NoError (t , m .Go (func (ctx context.Context ) {
60+ require .True (t , m .Go (func (ctx context.Context ) {
6061 <- ctx .Done ()
6162 }))
6263
@@ -79,7 +80,7 @@ func TestGoroutineManagerContextExpires(t *testing.T) {
7980 }
8081
8182 // Make sure new goroutines do not start after context expiry.
82- require .ErrorIs (t , m .Go (func (ctx context.Context ) {}), ErrStopping )
83+ require .False (t , m .Go (func (ctx context.Context ) {}))
8384
8485 // Stop will wait for all goroutines to stop.
8586 m .Stop ()
@@ -107,15 +108,50 @@ func TestGoroutineManagerStress(t *testing.T) {
107108 // implementation, this test crashes under `-race`.
108109 for i := 0 ; i < 100 ; i ++ {
109110 taskChan := make (chan struct {})
110- err := m .Go (func (ctx context.Context ) {
111+ ok := m .Go (func (ctx context.Context ) {
111112 close (taskChan )
112113 })
113114 // If goroutine was started, wait for its completion.
114- if err == nil {
115+ if ok {
115116 <- taskChan
116117 }
117118 }
118119
119120 // Wait for Stop to complete.
120121 <- stopChan
121122}
123+
124+ // TestGoroutineManagerStopsStress launches many Stop() calls in parallel with a
125+ // task exiting. It attempts to catch a race condition between wg.Done() and
126+ // wg.Wait() calls. According to documentation of wg.Wait() this is acceptable,
127+ // therefore this test passes even with -race.
128+ func TestGoroutineManagerStopsStress (t * testing.T ) {
129+ t .Parallel ()
130+
131+ m := NewGoroutineManager (context .Background ())
132+
133+ // jobChan is used to make the task to finish.
134+ jobChan := make (chan struct {})
135+
136+ // Start a task and wait inside it until we start calling Stop() method.
137+ ok := m .Go (func (ctx context.Context ) {
138+ <- jobChan
139+ })
140+ require .True (t , ok )
141+
142+ // Now launch many gorotines calling Stop() method in parallel.
143+ var wg sync.WaitGroup
144+ for i := 0 ; i < 100 ; i ++ {
145+ wg .Add (1 )
146+ go func () {
147+ defer wg .Done ()
148+ m .Stop ()
149+ }()
150+ }
151+
152+ // Exit the task in parallel with Stop() calls.
153+ close (jobChan )
154+
155+ // Wait until all the Stop() calls complete.
156+ wg .Wait ()
157+ }
0 commit comments