1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package sync_test
6
7import (
8	"internal/race"
9	"runtime"
10	. "sync"
11	"sync/atomic"
12	"testing"
13)
14
15func testWaitGroup(t *testing.T, wg1 *WaitGroup, wg2 *WaitGroup) {
16	n := 16
17	wg1.Add(n)
18	wg2.Add(n)
19	exited := make(chan bool, n)
20	for i := 0; i != n; i++ {
21		go func() {
22			wg1.Done()
23			wg2.Wait()
24			exited <- true
25		}()
26	}
27	wg1.Wait()
28	for i := 0; i != n; i++ {
29		select {
30		case <-exited:
31			t.Fatal("WaitGroup released group too soon")
32		default:
33		}
34		wg2.Done()
35	}
36	for i := 0; i != n; i++ {
37		<-exited // Will block if barrier fails to unlock someone.
38	}
39}
40
41func TestWaitGroup(t *testing.T) {
42	wg1 := &WaitGroup{}
43	wg2 := &WaitGroup{}
44
45	// Run the same test a few times to ensure barrier is in a proper state.
46	for i := 0; i != 8; i++ {
47		testWaitGroup(t, wg1, wg2)
48	}
49}
50
51func knownRacy(t *testing.T) {
52	if race.Enabled {
53		t.Skip("skipping known-racy test under the race detector")
54	}
55}
56
57func TestWaitGroupMisuse(t *testing.T) {
58	defer func() {
59		err := recover()
60		if err != "sync: negative WaitGroup counter" {
61			t.Fatalf("Unexpected panic: %#v", err)
62		}
63	}()
64	wg := &WaitGroup{}
65	wg.Add(1)
66	wg.Done()
67	wg.Done()
68	t.Fatal("Should panic")
69}
70
71func TestWaitGroupMisuse2(t *testing.T) {
72	knownRacy(t)
73	if runtime.NumCPU() <= 4 {
74		t.Skip("NumCPU<=4, skipping: this test requires parallelism")
75	}
76	defer func() {
77		err := recover()
78		if err != "sync: negative WaitGroup counter" &&
79			err != "sync: WaitGroup misuse: Add called concurrently with Wait" &&
80			err != "sync: WaitGroup is reused before previous Wait has returned" {
81			t.Fatalf("Unexpected panic: %#v", err)
82		}
83	}()
84	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
85	done := make(chan interface{}, 2)
86	// The detection is opportunistic, so we want it to panic
87	// at least in one run out of a million.
88	for i := 0; i < 1e6; i++ {
89		var wg WaitGroup
90		var here uint32
91		wg.Add(1)
92		go func() {
93			defer func() {
94				done <- recover()
95			}()
96			atomic.AddUint32(&here, 1)
97			for atomic.LoadUint32(&here) != 3 {
98				// spin
99			}
100			wg.Wait()
101		}()
102		go func() {
103			defer func() {
104				done <- recover()
105			}()
106			atomic.AddUint32(&here, 1)
107			for atomic.LoadUint32(&here) != 3 {
108				// spin
109			}
110			wg.Add(1) // This is the bad guy.
111			wg.Done()
112		}()
113		atomic.AddUint32(&here, 1)
114		for atomic.LoadUint32(&here) != 3 {
115			// spin
116		}
117		wg.Done()
118		for j := 0; j < 2; j++ {
119			if err := <-done; err != nil {
120				panic(err)
121			}
122		}
123	}
124	t.Fatal("Should panic")
125}
126
127func TestWaitGroupMisuse3(t *testing.T) {
128	knownRacy(t)
129	if runtime.NumCPU() <= 1 {
130		t.Skip("NumCPU==1, skipping: this test requires parallelism")
131	}
132	defer func() {
133		err := recover()
134		if err != "sync: negative WaitGroup counter" &&
135			err != "sync: WaitGroup misuse: Add called concurrently with Wait" &&
136			err != "sync: WaitGroup is reused before previous Wait has returned" {
137			t.Fatalf("Unexpected panic: %#v", err)
138		}
139	}()
140	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
141	done := make(chan interface{}, 2)
142	// The detection is opportunistically, so we want it to panic
143	// at least in one run out of a million.
144	for i := 0; i < 1e6; i++ {
145		var wg WaitGroup
146		wg.Add(1)
147		go func() {
148			defer func() {
149				done <- recover()
150			}()
151			wg.Done()
152		}()
153		go func() {
154			defer func() {
155				done <- recover()
156			}()
157			wg.Wait()
158			// Start reusing the wg before waiting for the Wait below to return.
159			wg.Add(1)
160			go func() {
161				wg.Done()
162			}()
163			wg.Wait()
164		}()
165		wg.Wait()
166		for j := 0; j < 2; j++ {
167			if err := <-done; err != nil {
168				panic(err)
169			}
170		}
171	}
172	t.Fatal("Should panic")
173}
174
175func TestWaitGroupRace(t *testing.T) {
176	// Run this test for about 1ms.
177	for i := 0; i < 1000; i++ {
178		wg := &WaitGroup{}
179		n := new(int32)
180		// spawn goroutine 1
181		wg.Add(1)
182		go func() {
183			atomic.AddInt32(n, 1)
184			wg.Done()
185		}()
186		// spawn goroutine 2
187		wg.Add(1)
188		go func() {
189			atomic.AddInt32(n, 1)
190			wg.Done()
191		}()
192		// Wait for goroutine 1 and 2
193		wg.Wait()
194		if atomic.LoadInt32(n) != 2 {
195			t.Fatal("Spurious wakeup from Wait")
196		}
197	}
198}
199
200func TestWaitGroupAlign(t *testing.T) {
201	type X struct {
202		x  byte
203		wg WaitGroup
204	}
205	var x X
206	x.wg.Add(1)
207	go func(x *X) {
208		x.wg.Done()
209	}(&x)
210	x.wg.Wait()
211}
212
213func BenchmarkWaitGroupUncontended(b *testing.B) {
214	type PaddedWaitGroup struct {
215		WaitGroup
216		pad [128]uint8
217	}
218	b.RunParallel(func(pb *testing.PB) {
219		var wg PaddedWaitGroup
220		for pb.Next() {
221			wg.Add(1)
222			wg.Done()
223			wg.Wait()
224		}
225	})
226}
227
228func benchmarkWaitGroupAddDone(b *testing.B, localWork int) {
229	var wg WaitGroup
230	b.RunParallel(func(pb *testing.PB) {
231		foo := 0
232		for pb.Next() {
233			wg.Add(1)
234			for i := 0; i < localWork; i++ {
235				foo *= 2
236				foo /= 2
237			}
238			wg.Done()
239		}
240		_ = foo
241	})
242}
243
244func BenchmarkWaitGroupAddDone(b *testing.B) {
245	benchmarkWaitGroupAddDone(b, 0)
246}
247
248func BenchmarkWaitGroupAddDoneWork(b *testing.B) {
249	benchmarkWaitGroupAddDone(b, 100)
250}
251
252func benchmarkWaitGroupWait(b *testing.B, localWork int) {
253	var wg WaitGroup
254	b.RunParallel(func(pb *testing.PB) {
255		foo := 0
256		for pb.Next() {
257			wg.Wait()
258			for i := 0; i < localWork; i++ {
259				foo *= 2
260				foo /= 2
261			}
262		}
263		_ = foo
264	})
265}
266
267func BenchmarkWaitGroupWait(b *testing.B) {
268	benchmarkWaitGroupWait(b, 0)
269}
270
271func BenchmarkWaitGroupWaitWork(b *testing.B) {
272	benchmarkWaitGroupWait(b, 100)
273}
274
275func BenchmarkWaitGroupActuallyWait(b *testing.B) {
276	b.ReportAllocs()
277	b.RunParallel(func(pb *testing.PB) {
278		for pb.Next() {
279			var wg WaitGroup
280			wg.Add(1)
281			go func() {
282				wg.Done()
283			}()
284			wg.Wait()
285		}
286	})
287}
288