1// Copyright (c) 2016 Uber Technologies, Inc.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21package atomic
22
23import (
24	"errors"
25	"math"
26	"runtime"
27	"sync"
28	"sync/atomic"
29	"testing"
30	"time"
31)
32
33const (
34	_parallelism = 4
35	_iterations  = 1000
36)
37
38var _stressTests = map[string]func() func(){
39	"i32/std":  stressStdInt32,
40	"i32":      stressInt32,
41	"i64/std":  stressStdInt64,
42	"i64":      stressInt64,
43	"u32/std":  stressStdUint32,
44	"u32":      stressUint32,
45	"u64/std":  stressStdUint64,
46	"u64":      stressUint64,
47	"f64":      stressFloat64,
48	"bool":     stressBool,
49	"string":   stressString,
50	"duration": stressDuration,
51	"error":    stressError,
52	"time":     stressTime,
53}
54
55func TestStress(t *testing.T) {
56	for name, ff := range _stressTests {
57		t.Run(name, func(t *testing.T) {
58			defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(_parallelism))
59
60			start := make(chan struct{})
61			var wg sync.WaitGroup
62			wg.Add(_parallelism)
63			f := ff()
64			for i := 0; i < _parallelism; i++ {
65				go func() {
66					defer wg.Done()
67					<-start
68					for j := 0; j < _iterations; j++ {
69						f()
70					}
71				}()
72			}
73			close(start)
74			wg.Wait()
75		})
76	}
77}
78
79func BenchmarkStress(b *testing.B) {
80	for name, ff := range _stressTests {
81		b.Run(name, func(b *testing.B) {
82			f := ff()
83
84			b.Run("serial", func(b *testing.B) {
85				for i := 0; i < b.N; i++ {
86					f()
87				}
88			})
89
90			b.Run("parallel", func(b *testing.B) {
91				b.RunParallel(func(pb *testing.PB) {
92					for pb.Next() {
93						f()
94					}
95				})
96			})
97		})
98	}
99}
100
101func stressStdInt32() func() {
102	var atom int32
103	return func() {
104		atomic.LoadInt32(&atom)
105		atomic.AddInt32(&atom, 1)
106		atomic.AddInt32(&atom, -2)
107		atomic.AddInt32(&atom, 1)
108		atomic.AddInt32(&atom, -1)
109		atomic.CompareAndSwapInt32(&atom, 1, 0)
110		atomic.SwapInt32(&atom, 5)
111		atomic.StoreInt32(&atom, 1)
112	}
113}
114
115func stressInt32() func() {
116	var atom Int32
117	return func() {
118		atom.Load()
119		atom.Add(1)
120		atom.Sub(2)
121		atom.Inc()
122		atom.Dec()
123		atom.CAS(1, 0)
124		atom.Swap(5)
125		atom.Store(1)
126	}
127}
128
129func stressStdInt64() func() {
130	var atom int64
131	return func() {
132		atomic.LoadInt64(&atom)
133		atomic.AddInt64(&atom, 1)
134		atomic.AddInt64(&atom, -2)
135		atomic.AddInt64(&atom, 1)
136		atomic.AddInt64(&atom, -1)
137		atomic.CompareAndSwapInt64(&atom, 1, 0)
138		atomic.SwapInt64(&atom, 5)
139		atomic.StoreInt64(&atom, 1)
140	}
141}
142
143func stressInt64() func() {
144	var atom Int64
145	return func() {
146		atom.Load()
147		atom.Add(1)
148		atom.Sub(2)
149		atom.Inc()
150		atom.Dec()
151		atom.CAS(1, 0)
152		atom.Swap(5)
153		atom.Store(1)
154	}
155}
156
157func stressStdUint32() func() {
158	var atom uint32
159	return func() {
160		atomic.LoadUint32(&atom)
161		atomic.AddUint32(&atom, 1)
162		// Adding `MaxUint32` is the same as subtracting 1
163		atomic.AddUint32(&atom, math.MaxUint32-1)
164		atomic.AddUint32(&atom, 1)
165		atomic.AddUint32(&atom, math.MaxUint32)
166		atomic.CompareAndSwapUint32(&atom, 1, 0)
167		atomic.SwapUint32(&atom, 5)
168		atomic.StoreUint32(&atom, 1)
169	}
170}
171
172func stressUint32() func() {
173	var atom Uint32
174	return func() {
175		atom.Load()
176		atom.Add(1)
177		atom.Sub(2)
178		atom.Inc()
179		atom.Dec()
180		atom.CAS(1, 0)
181		atom.Swap(5)
182		atom.Store(1)
183	}
184}
185
186func stressStdUint64() func() {
187	var atom uint64
188	return func() {
189		atomic.LoadUint64(&atom)
190		atomic.AddUint64(&atom, 1)
191		// Adding `MaxUint64` is the same as subtracting 1
192		atomic.AddUint64(&atom, math.MaxUint64-1)
193		atomic.AddUint64(&atom, 1)
194		atomic.AddUint64(&atom, math.MaxUint64)
195		atomic.CompareAndSwapUint64(&atom, 1, 0)
196		atomic.SwapUint64(&atom, 5)
197		atomic.StoreUint64(&atom, 1)
198	}
199}
200
201func stressUint64() func() {
202	var atom Uint64
203	return func() {
204		atom.Load()
205		atom.Add(1)
206		atom.Sub(2)
207		atom.Inc()
208		atom.Dec()
209		atom.CAS(1, 0)
210		atom.Swap(5)
211		atom.Store(1)
212	}
213}
214
215func stressFloat64() func() {
216	var atom Float64
217	return func() {
218		atom.Load()
219		atom.CAS(1.0, 0.1)
220		atom.Add(1.1)
221		atom.Sub(0.2)
222		atom.Store(1.0)
223	}
224}
225
226func stressBool() func() {
227	var atom Bool
228	return func() {
229		atom.Load()
230		atom.Store(false)
231		atom.Swap(true)
232		atom.CAS(true, false)
233		atom.CAS(true, false)
234		atom.Load()
235		atom.Toggle()
236		atom.Toggle()
237	}
238}
239
240func stressString() func() {
241	var atom String
242	return func() {
243		atom.Load()
244		atom.Store("abc")
245		atom.Load()
246		atom.Store("def")
247		atom.Load()
248		atom.Store("")
249	}
250}
251
252func stressDuration() func() {
253	var atom = NewDuration(0)
254	return func() {
255		atom.Load()
256		atom.Add(1)
257		atom.Sub(2)
258		atom.CAS(1, 0)
259		atom.Swap(5)
260		atom.Store(1)
261	}
262}
263
264func stressError() func() {
265	var atom = NewError(nil)
266	var err1 = errors.New("err1")
267	var err2 = errors.New("err2")
268	return func() {
269		atom.Load()
270		atom.Store(err1)
271		atom.Load()
272		atom.Store(err2)
273		atom.Load()
274		atom.Store(nil)
275	}
276}
277
278func stressTime() func() {
279	var atom = NewTime(time.Date(2021, 6, 17, 9, 0, 0, 0, time.UTC))
280	var dayAgo = time.Date(2021, 6, 16, 9, 0, 0, 0, time.UTC)
281	var weekAgo = time.Date(2021, 6, 10, 9, 0, 0, 0, time.UTC)
282	return func() {
283		atom.Load()
284		atom.Store(dayAgo)
285		atom.Load()
286		atom.Store(weekAgo)
287		atom.Store(time.Time{})
288	}
289}
290