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