Black Lives Matter. Support the Equal Justice Initiative.

Source file src/sync/waitgroup_test.go

Documentation: 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  // pollUntilEqual blocks until v, loaded atomically, is
    72  // equal to the target.
    73  func 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  
    86  func 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  
   136  func 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{}, 3)
   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  		go func() {
   175  			defer func() {
   176  				done <- recover()
   177  			}()
   178  			wg.Wait()
   179  		}()
   180  		for j := 0; j < 3; j++ {
   181  			if err := <-done; err != nil {
   182  				panic(err)
   183  			}
   184  		}
   185  	}
   186  	t.Fatal("Should panic")
   187  }
   188  
   189  func TestWaitGroupRace(t *testing.T) {
   190  	// Run this test for about 1ms.
   191  	for i := 0; i < 1000; i++ {
   192  		wg := &WaitGroup{}
   193  		n := new(int32)
   194  		// spawn goroutine 1
   195  		wg.Add(1)
   196  		go func() {
   197  			atomic.AddInt32(n, 1)
   198  			wg.Done()
   199  		}()
   200  		// spawn goroutine 2
   201  		wg.Add(1)
   202  		go func() {
   203  			atomic.AddInt32(n, 1)
   204  			wg.Done()
   205  		}()
   206  		// Wait for goroutine 1 and 2
   207  		wg.Wait()
   208  		if atomic.LoadInt32(n) != 2 {
   209  			t.Fatal("Spurious wakeup from Wait")
   210  		}
   211  	}
   212  }
   213  
   214  func TestWaitGroupAlign(t *testing.T) {
   215  	type X struct {
   216  		x  byte
   217  		wg WaitGroup
   218  	}
   219  	var x X
   220  	x.wg.Add(1)
   221  	go func(x *X) {
   222  		x.wg.Done()
   223  	}(&x)
   224  	x.wg.Wait()
   225  }
   226  
   227  func BenchmarkWaitGroupUncontended(b *testing.B) {
   228  	type PaddedWaitGroup struct {
   229  		WaitGroup
   230  		pad [128]uint8
   231  	}
   232  	b.RunParallel(func(pb *testing.PB) {
   233  		var wg PaddedWaitGroup
   234  		for pb.Next() {
   235  			wg.Add(1)
   236  			wg.Done()
   237  			wg.Wait()
   238  		}
   239  	})
   240  }
   241  
   242  func benchmarkWaitGroupAddDone(b *testing.B, localWork int) {
   243  	var wg WaitGroup
   244  	b.RunParallel(func(pb *testing.PB) {
   245  		foo := 0
   246  		for pb.Next() {
   247  			wg.Add(1)
   248  			for i := 0; i < localWork; i++ {
   249  				foo *= 2
   250  				foo /= 2
   251  			}
   252  			wg.Done()
   253  		}
   254  		_ = foo
   255  	})
   256  }
   257  
   258  func BenchmarkWaitGroupAddDone(b *testing.B) {
   259  	benchmarkWaitGroupAddDone(b, 0)
   260  }
   261  
   262  func BenchmarkWaitGroupAddDoneWork(b *testing.B) {
   263  	benchmarkWaitGroupAddDone(b, 100)
   264  }
   265  
   266  func benchmarkWaitGroupWait(b *testing.B, localWork int) {
   267  	var wg WaitGroup
   268  	b.RunParallel(func(pb *testing.PB) {
   269  		foo := 0
   270  		for pb.Next() {
   271  			wg.Wait()
   272  			for i := 0; i < localWork; i++ {
   273  				foo *= 2
   274  				foo /= 2
   275  			}
   276  		}
   277  		_ = foo
   278  	})
   279  }
   280  
   281  func BenchmarkWaitGroupWait(b *testing.B) {
   282  	benchmarkWaitGroupWait(b, 0)
   283  }
   284  
   285  func BenchmarkWaitGroupWaitWork(b *testing.B) {
   286  	benchmarkWaitGroupWait(b, 100)
   287  }
   288  
   289  func BenchmarkWaitGroupActuallyWait(b *testing.B) {
   290  	b.ReportAllocs()
   291  	b.RunParallel(func(pb *testing.PB) {
   292  		for pb.Next() {
   293  			var wg WaitGroup
   294  			wg.Add(1)
   295  			go func() {
   296  				wg.Done()
   297  			}()
   298  			wg.Wait()
   299  		}
   300  	})
   301  }
   302  

View as plain text