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
71// pollUntilEqual blocks until v, loaded atomically, is
72// equal to the target.
73func pollUntilEqual(v *uint32, target uint32) {
74	for {
75		for i := 0; i < 1e3; i++ {
76			if atomic.LoadUint32(v) == target {
77				return
78			}
79		}
80		// yield to avoid deadlock with the garbage collector
81		// see issue #20072
82		runtime.Gosched()
83	}
84}
85
86func TestWaitGroupMisuse2(t *testing.T) {
87	knownRacy(t)
88	if runtime.NumCPU() <= 4 {
89		t.Skip("NumCPU<=4, skipping: this test requires parallelism")
90	}
91	defer func() {
92		err := recover()
93		if err != "sync: negative WaitGroup counter" &&
94			err != "sync: WaitGroup misuse: Add called concurrently with Wait" &&
95			err != "sync: WaitGroup is reused before previous Wait has returned" {
96			t.Fatalf("Unexpected panic: %#v", err)
97		}
98	}()
99	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
100	done := make(chan interface{}, 2)
101	// The detection is opportunistic, so we want it to panic
102	// at least in one run out of a million.
103	for i := 0; i < 1e6; i++ {
104		var wg WaitGroup
105		var here uint32
106		wg.Add(1)
107		go func() {
108			defer func() {
109				done <- recover()
110			}()
111			atomic.AddUint32(&here, 1)
112			pollUntilEqual(&here, 3)
113			wg.Wait()
114		}()
115		go func() {
116			defer func() {
117				done <- recover()
118			}()
119			atomic.AddUint32(&here, 1)
120			pollUntilEqual(&here, 3)
121			wg.Add(1) // This is the bad guy.
122			wg.Done()
123		}()
124		atomic.AddUint32(&here, 1)
125		pollUntilEqual(&here, 3)
126		wg.Done()
127		for j := 0; j < 2; j++ {
128			if err := <-done; err != nil {
129				panic(err)
130			}
131		}
132	}
133	t.Fatal("Should panic")
134}
135
136func TestWaitGroupMisuse3(t *testing.T) {
137	knownRacy(t)
138	if runtime.NumCPU() <= 1 {
139		t.Skip("NumCPU==1, skipping: this test requires parallelism")
140	}
141	defer func() {
142		err := recover()
143		if err != "sync: negative WaitGroup counter" &&
144			err != "sync: WaitGroup misuse: Add called concurrently with Wait" &&
145			err != "sync: WaitGroup is reused before previous Wait has returned" {
146			t.Fatalf("Unexpected panic: %#v", err)
147		}
148	}()
149	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
150	done := make(chan interface{}, 2)
151	// The detection is opportunistically, so we want it to panic
152	// at least in one run out of a million.
153	for i := 0; i < 1e6; i++ {
154		var wg WaitGroup
155		wg.Add(1)
156		go func() {
157			defer func() {
158				done <- recover()
159			}()
160			wg.Done()
161		}()
162		go func() {
163			defer func() {
164				done <- recover()
165			}()
166			wg.Wait()
167			// Start reusing the wg before waiting for the Wait below to return.
168			wg.Add(1)
169			go func() {
170				wg.Done()
171			}()
172			wg.Wait()
173		}()
174		wg.Wait()
175		for j := 0; j < 2; j++ {
176			if err := <-done; err != nil {
177				panic(err)
178			}
179		}
180	}
181	t.Fatal("Should panic")
182}
183
184func TestWaitGroupRace(t *testing.T) {
185	// Run this test for about 1ms.
186	for i := 0; i < 1000; i++ {
187		wg := &WaitGroup{}
188		n := new(int32)
189		// spawn goroutine 1
190		wg.Add(1)
191		go func() {
192			atomic.AddInt32(n, 1)
193			wg.Done()
194		}()
195		// spawn goroutine 2
196		wg.Add(1)
197		go func() {
198			atomic.AddInt32(n, 1)
199			wg.Done()
200		}()
201		// Wait for goroutine 1 and 2
202		wg.Wait()
203		if atomic.LoadInt32(n) != 2 {
204			t.Fatal("Spurious wakeup from Wait")
205		}
206	}
207}
208
209func TestWaitGroupAlign(t *testing.T) {
210	type X struct {
211		x  byte
212		wg WaitGroup
213	}
214	var x X
215	x.wg.Add(1)
216	go func(x *X) {
217		x.wg.Done()
218	}(&x)
219	x.wg.Wait()
220}
221
222func BenchmarkWaitGroupUncontended(b *testing.B) {
223	type PaddedWaitGroup struct {
224		WaitGroup
225		pad [128]uint8
226	}
227	b.RunParallel(func(pb *testing.PB) {
228		var wg PaddedWaitGroup
229		for pb.Next() {
230			wg.Add(1)
231			wg.Done()
232			wg.Wait()
233		}
234	})
235}
236
237func benchmarkWaitGroupAddDone(b *testing.B, localWork int) {
238	var wg WaitGroup
239	b.RunParallel(func(pb *testing.PB) {
240		foo := 0
241		for pb.Next() {
242			wg.Add(1)
243			for i := 0; i < localWork; i++ {
244				foo *= 2
245				foo /= 2
246			}
247			wg.Done()
248		}
249		_ = foo
250	})
251}
252
253func BenchmarkWaitGroupAddDone(b *testing.B) {
254	benchmarkWaitGroupAddDone(b, 0)
255}
256
257func BenchmarkWaitGroupAddDoneWork(b *testing.B) {
258	benchmarkWaitGroupAddDone(b, 100)
259}
260
261func benchmarkWaitGroupWait(b *testing.B, localWork int) {
262	var wg WaitGroup
263	b.RunParallel(func(pb *testing.PB) {
264		foo := 0
265		for pb.Next() {
266			wg.Wait()
267			for i := 0; i < localWork; i++ {
268				foo *= 2
269				foo /= 2
270			}
271		}
272		_ = foo
273	})
274}
275
276func BenchmarkWaitGroupWait(b *testing.B) {
277	benchmarkWaitGroupWait(b, 0)
278}
279
280func BenchmarkWaitGroupWaitWork(b *testing.B) {
281	benchmarkWaitGroupWait(b, 100)
282}
283
284func BenchmarkWaitGroupActuallyWait(b *testing.B) {
285	b.ReportAllocs()
286	b.RunParallel(func(pb *testing.PB) {
287		for pb.Next() {
288			var wg WaitGroup
289			wg.Add(1)
290			go func() {
291				wg.Done()
292			}()
293			wg.Wait()
294		}
295	})
296}
297