...
Run Format

Source file src/sync/waitgroup_test.go

Documentation: sync

  // Copyright 2011 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  package sync_test
  
  import (
  	"internal/race"
  	"runtime"
  	. "sync"
  	"sync/atomic"
  	"testing"
  )
  
  func testWaitGroup(t *testing.T, wg1 *WaitGroup, wg2 *WaitGroup) {
  	n := 16
  	wg1.Add(n)
  	wg2.Add(n)
  	exited := make(chan bool, n)
  	for i := 0; i != n; i++ {
  		go func() {
  			wg1.Done()
  			wg2.Wait()
  			exited <- true
  		}()
  	}
  	wg1.Wait()
  	for i := 0; i != n; i++ {
  		select {
  		case <-exited:
  			t.Fatal("WaitGroup released group too soon")
  		default:
  		}
  		wg2.Done()
  	}
  	for i := 0; i != n; i++ {
  		<-exited // Will block if barrier fails to unlock someone.
  	}
  }
  
  func TestWaitGroup(t *testing.T) {
  	wg1 := &WaitGroup{}
  	wg2 := &WaitGroup{}
  
  	// Run the same test a few times to ensure barrier is in a proper state.
  	for i := 0; i != 8; i++ {
  		testWaitGroup(t, wg1, wg2)
  	}
  }
  
  func knownRacy(t *testing.T) {
  	if race.Enabled {
  		t.Skip("skipping known-racy test under the race detector")
  	}
  }
  
  func TestWaitGroupMisuse(t *testing.T) {
  	defer func() {
  		err := recover()
  		if err != "sync: negative WaitGroup counter" {
  			t.Fatalf("Unexpected panic: %#v", err)
  		}
  	}()
  	wg := &WaitGroup{}
  	wg.Add(1)
  	wg.Done()
  	wg.Done()
  	t.Fatal("Should panic")
  }
  
  func TestWaitGroupMisuse2(t *testing.T) {
  	knownRacy(t)
  	if runtime.NumCPU() <= 4 {
  		t.Skip("NumCPU<=4, skipping: this test requires parallelism")
  	}
  	defer func() {
  		err := recover()
  		if err != "sync: negative WaitGroup counter" &&
  			err != "sync: WaitGroup misuse: Add called concurrently with Wait" &&
  			err != "sync: WaitGroup is reused before previous Wait has returned" {
  			t.Fatalf("Unexpected panic: %#v", err)
  		}
  	}()
  	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
  	done := make(chan interface{}, 2)
  	// The detection is opportunistic, so we want it to panic
  	// at least in one run out of a million.
  	for i := 0; i < 1e6; i++ {
  		var wg WaitGroup
  		var here uint32
  		wg.Add(1)
  		go func() {
  			defer func() {
  				done <- recover()
  			}()
  			atomic.AddUint32(&here, 1)
  			for atomic.LoadUint32(&here) != 3 {
  				// spin
  			}
  			wg.Wait()
  		}()
  		go func() {
  			defer func() {
  				done <- recover()
  			}()
  			atomic.AddUint32(&here, 1)
  			for atomic.LoadUint32(&here) != 3 {
  				// spin
  			}
  			wg.Add(1) // This is the bad guy.
  			wg.Done()
  		}()
  		atomic.AddUint32(&here, 1)
  		for atomic.LoadUint32(&here) != 3 {
  			// spin
  		}
  		wg.Done()
  		for j := 0; j < 2; j++ {
  			if err := <-done; err != nil {
  				panic(err)
  			}
  		}
  	}
  	t.Fatal("Should panic")
  }
  
  func TestWaitGroupMisuse3(t *testing.T) {
  	knownRacy(t)
  	if runtime.NumCPU() <= 1 {
  		t.Skip("NumCPU==1, skipping: this test requires parallelism")
  	}
  	defer func() {
  		err := recover()
  		if err != "sync: negative WaitGroup counter" &&
  			err != "sync: WaitGroup misuse: Add called concurrently with Wait" &&
  			err != "sync: WaitGroup is reused before previous Wait has returned" {
  			t.Fatalf("Unexpected panic: %#v", err)
  		}
  	}()
  	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
  	done := make(chan interface{}, 2)
  	// The detection is opportunistically, so we want it to panic
  	// at least in one run out of a million.
  	for i := 0; i < 1e6; i++ {
  		var wg WaitGroup
  		wg.Add(1)
  		go func() {
  			defer func() {
  				done <- recover()
  			}()
  			wg.Done()
  		}()
  		go func() {
  			defer func() {
  				done <- recover()
  			}()
  			wg.Wait()
  			// Start reusing the wg before waiting for the Wait below to return.
  			wg.Add(1)
  			go func() {
  				wg.Done()
  			}()
  			wg.Wait()
  		}()
  		wg.Wait()
  		for j := 0; j < 2; j++ {
  			if err := <-done; err != nil {
  				panic(err)
  			}
  		}
  	}
  	t.Fatal("Should panic")
  }
  
  func TestWaitGroupRace(t *testing.T) {
  	// Run this test for about 1ms.
  	for i := 0; i < 1000; i++ {
  		wg := &WaitGroup{}
  		n := new(int32)
  		// spawn goroutine 1
  		wg.Add(1)
  		go func() {
  			atomic.AddInt32(n, 1)
  			wg.Done()
  		}()
  		// spawn goroutine 2
  		wg.Add(1)
  		go func() {
  			atomic.AddInt32(n, 1)
  			wg.Done()
  		}()
  		// Wait for goroutine 1 and 2
  		wg.Wait()
  		if atomic.LoadInt32(n) != 2 {
  			t.Fatal("Spurious wakeup from Wait")
  		}
  	}
  }
  
  func TestWaitGroupAlign(t *testing.T) {
  	type X struct {
  		x  byte
  		wg WaitGroup
  	}
  	var x X
  	x.wg.Add(1)
  	go func(x *X) {
  		x.wg.Done()
  	}(&x)
  	x.wg.Wait()
  }
  
  func BenchmarkWaitGroupUncontended(b *testing.B) {
  	type PaddedWaitGroup struct {
  		WaitGroup
  		pad [128]uint8
  	}
  	b.RunParallel(func(pb *testing.PB) {
  		var wg PaddedWaitGroup
  		for pb.Next() {
  			wg.Add(1)
  			wg.Done()
  			wg.Wait()
  		}
  	})
  }
  
  func benchmarkWaitGroupAddDone(b *testing.B, localWork int) {
  	var wg WaitGroup
  	b.RunParallel(func(pb *testing.PB) {
  		foo := 0
  		for pb.Next() {
  			wg.Add(1)
  			for i := 0; i < localWork; i++ {
  				foo *= 2
  				foo /= 2
  			}
  			wg.Done()
  		}
  		_ = foo
  	})
  }
  
  func BenchmarkWaitGroupAddDone(b *testing.B) {
  	benchmarkWaitGroupAddDone(b, 0)
  }
  
  func BenchmarkWaitGroupAddDoneWork(b *testing.B) {
  	benchmarkWaitGroupAddDone(b, 100)
  }
  
  func benchmarkWaitGroupWait(b *testing.B, localWork int) {
  	var wg WaitGroup
  	b.RunParallel(func(pb *testing.PB) {
  		foo := 0
  		for pb.Next() {
  			wg.Wait()
  			for i := 0; i < localWork; i++ {
  				foo *= 2
  				foo /= 2
  			}
  		}
  		_ = foo
  	})
  }
  
  func BenchmarkWaitGroupWait(b *testing.B) {
  	benchmarkWaitGroupWait(b, 0)
  }
  
  func BenchmarkWaitGroupWaitWork(b *testing.B) {
  	benchmarkWaitGroupWait(b, 100)
  }
  
  func BenchmarkWaitGroupActuallyWait(b *testing.B) {
  	b.ReportAllocs()
  	b.RunParallel(func(pb *testing.PB) {
  		for pb.Next() {
  			var wg WaitGroup
  			wg.Add(1)
  			go func() {
  				wg.Done()
  			}()
  			wg.Wait()
  		}
  	})
  }
  

View as plain text