1// Copyright 2009 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// GOMAXPROCS=10 go test
6
7package sync_test
8
9import (
10	"fmt"
11	"internal/testenv"
12	"os"
13	"os/exec"
14	"runtime"
15	"strings"
16	. "sync"
17	"testing"
18	"time"
19)
20
21func HammerSemaphore(s *uint32, loops int, cdone chan bool) {
22	for i := 0; i < loops; i++ {
23		Runtime_Semacquire(s)
24		Runtime_Semrelease(s, false, 0)
25	}
26	cdone <- true
27}
28
29func TestSemaphore(t *testing.T) {
30	s := new(uint32)
31	*s = 1
32	c := make(chan bool)
33	for i := 0; i < 10; i++ {
34		go HammerSemaphore(s, 1000, c)
35	}
36	for i := 0; i < 10; i++ {
37		<-c
38	}
39}
40
41func BenchmarkUncontendedSemaphore(b *testing.B) {
42	s := new(uint32)
43	*s = 1
44	HammerSemaphore(s, b.N, make(chan bool, 2))
45}
46
47func BenchmarkContendedSemaphore(b *testing.B) {
48	b.StopTimer()
49	s := new(uint32)
50	*s = 1
51	c := make(chan bool)
52	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
53	b.StartTimer()
54
55	go HammerSemaphore(s, b.N/2, c)
56	go HammerSemaphore(s, b.N/2, c)
57	<-c
58	<-c
59}
60
61func HammerMutex(m *Mutex, loops int, cdone chan bool) {
62	for i := 0; i < loops; i++ {
63		m.Lock()
64		m.Unlock()
65	}
66	cdone <- true
67}
68
69func TestMutex(t *testing.T) {
70	if n := runtime.SetMutexProfileFraction(1); n != 0 {
71		t.Logf("got mutexrate %d expected 0", n)
72	}
73	defer runtime.SetMutexProfileFraction(0)
74	m := new(Mutex)
75	c := make(chan bool)
76	for i := 0; i < 10; i++ {
77		go HammerMutex(m, 1000, c)
78	}
79	for i := 0; i < 10; i++ {
80		<-c
81	}
82}
83
84var misuseTests = []struct {
85	name string
86	f    func()
87}{
88	{
89		"Mutex.Unlock",
90		func() {
91			var mu Mutex
92			mu.Unlock()
93		},
94	},
95	{
96		"Mutex.Unlock2",
97		func() {
98			var mu Mutex
99			mu.Lock()
100			mu.Unlock()
101			mu.Unlock()
102		},
103	},
104	{
105		"RWMutex.Unlock",
106		func() {
107			var mu RWMutex
108			mu.Unlock()
109		},
110	},
111	{
112		"RWMutex.Unlock2",
113		func() {
114			var mu RWMutex
115			mu.RLock()
116			mu.Unlock()
117		},
118	},
119	{
120		"RWMutex.Unlock3",
121		func() {
122			var mu RWMutex
123			mu.Lock()
124			mu.Unlock()
125			mu.Unlock()
126		},
127	},
128	{
129		"RWMutex.RUnlock",
130		func() {
131			var mu RWMutex
132			mu.RUnlock()
133		},
134	},
135	{
136		"RWMutex.RUnlock2",
137		func() {
138			var mu RWMutex
139			mu.Lock()
140			mu.RUnlock()
141		},
142	},
143	{
144		"RWMutex.RUnlock3",
145		func() {
146			var mu RWMutex
147			mu.RLock()
148			mu.RUnlock()
149			mu.RUnlock()
150		},
151	},
152}
153
154func init() {
155	if len(os.Args) == 3 && os.Args[1] == "TESTMISUSE" {
156		for _, test := range misuseTests {
157			if test.name == os.Args[2] {
158				func() {
159					defer func() { recover() }()
160					test.f()
161				}()
162				fmt.Printf("test completed\n")
163				os.Exit(0)
164			}
165		}
166		fmt.Printf("unknown test\n")
167		os.Exit(0)
168	}
169}
170
171func TestMutexMisuse(t *testing.T) {
172	testenv.MustHaveExec(t)
173	for _, test := range misuseTests {
174		out, err := exec.Command(os.Args[0], "TESTMISUSE", test.name).CombinedOutput()
175		if err == nil || !strings.Contains(string(out), "unlocked") {
176			t.Errorf("%s: did not find failure with message about unlocked lock: %s\n%s\n", test.name, err, out)
177		}
178	}
179}
180
181func TestMutexFairness(t *testing.T) {
182	var mu Mutex
183	stop := make(chan bool)
184	defer close(stop)
185	go func() {
186		for {
187			mu.Lock()
188			time.Sleep(100 * time.Microsecond)
189			mu.Unlock()
190			select {
191			case <-stop:
192				return
193			default:
194			}
195		}
196	}()
197	done := make(chan bool)
198	go func() {
199		for i := 0; i < 10; i++ {
200			time.Sleep(100 * time.Microsecond)
201			mu.Lock()
202			mu.Unlock()
203		}
204		done <- true
205	}()
206	select {
207	case <-done:
208	case <-time.After(10 * time.Second):
209		t.Fatalf("can't acquire Mutex in 10 seconds")
210	}
211}
212
213func BenchmarkMutexUncontended(b *testing.B) {
214	type PaddedMutex struct {
215		Mutex
216		pad [128]uint8
217	}
218	b.RunParallel(func(pb *testing.PB) {
219		var mu PaddedMutex
220		for pb.Next() {
221			mu.Lock()
222			mu.Unlock()
223		}
224	})
225}
226
227func benchmarkMutex(b *testing.B, slack, work bool) {
228	var mu Mutex
229	if slack {
230		b.SetParallelism(10)
231	}
232	b.RunParallel(func(pb *testing.PB) {
233		foo := 0
234		for pb.Next() {
235			mu.Lock()
236			mu.Unlock()
237			if work {
238				for i := 0; i < 100; i++ {
239					foo *= 2
240					foo /= 2
241				}
242			}
243		}
244		_ = foo
245	})
246}
247
248func BenchmarkMutex(b *testing.B) {
249	benchmarkMutex(b, false, false)
250}
251
252func BenchmarkMutexSlack(b *testing.B) {
253	benchmarkMutex(b, true, false)
254}
255
256func BenchmarkMutexWork(b *testing.B) {
257	benchmarkMutex(b, false, true)
258}
259
260func BenchmarkMutexWorkSlack(b *testing.B) {
261	benchmarkMutex(b, true, true)
262}
263
264func BenchmarkMutexNoSpin(b *testing.B) {
265	// This benchmark models a situation where spinning in the mutex should be
266	// non-profitable and allows to confirm that spinning does not do harm.
267	// To achieve this we create excess of goroutines most of which do local work.
268	// These goroutines yield during local work, so that switching from
269	// a blocked goroutine to other goroutines is profitable.
270	// As a matter of fact, this benchmark still triggers some spinning in the mutex.
271	var m Mutex
272	var acc0, acc1 uint64
273	b.SetParallelism(4)
274	b.RunParallel(func(pb *testing.PB) {
275		c := make(chan bool)
276		var data [4 << 10]uint64
277		for i := 0; pb.Next(); i++ {
278			if i%4 == 0 {
279				m.Lock()
280				acc0 -= 100
281				acc1 += 100
282				m.Unlock()
283			} else {
284				for i := 0; i < len(data); i += 4 {
285					data[i]++
286				}
287				// Elaborate way to say runtime.Gosched
288				// that does not put the goroutine onto global runq.
289				go func() {
290					c <- true
291				}()
292				<-c
293			}
294		}
295	})
296}
297
298func BenchmarkMutexSpin(b *testing.B) {
299	// This benchmark models a situation where spinning in the mutex should be
300	// profitable. To achieve this we create a goroutine per-proc.
301	// These goroutines access considerable amount of local data so that
302	// unnecessary rescheduling is penalized by cache misses.
303	var m Mutex
304	var acc0, acc1 uint64
305	b.RunParallel(func(pb *testing.PB) {
306		var data [16 << 10]uint64
307		for i := 0; pb.Next(); i++ {
308			m.Lock()
309			acc0 -= 100
310			acc1 += 100
311			m.Unlock()
312			for i := 0; i < len(data); i += 4 {
313				data[i]++
314			}
315		}
316	})
317}
318