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 	"internal/race"
      9 	"runtime"
     10 	. "sync"
     11 	"sync/atomic"
     12 	"testing"
     13 )
     14 
     15 func 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 
     41 func 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 
     51 func knownRacy(t *testing.T) {
     52 	if race.Enabled {
     53 		t.Skip("skipping known-racy test under the race detector")
     54 	}
     55 }
     56 
     57 func 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 func 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 
    127 func 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 
    175 func 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 
    200 func 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 
    213 func 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 
    228 func 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 
    244 func BenchmarkWaitGroupAddDone(b *testing.B) {
    245 	benchmarkWaitGroupAddDone(b, 0)
    246 }
    247 
    248 func BenchmarkWaitGroupAddDoneWork(b *testing.B) {
    249 	benchmarkWaitGroupAddDone(b, 100)
    250 }
    251 
    252 func 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 
    267 func BenchmarkWaitGroupWait(b *testing.B) {
    268 	benchmarkWaitGroupWait(b, 0)
    269 }
    270 
    271 func BenchmarkWaitGroupWaitWork(b *testing.B) {
    272 	benchmarkWaitGroupWait(b, 100)
    273 }
    274 
    275 func 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