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