blob: 08f70c5ebc75ded4de5aabb91ab5326af3077f3f [file] [log] [blame]
// 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 atomic_test
import (
"runtime"
. "sync/atomic"
"testing"
"unsafe"
)
// Tests of correct behavior, without contention.
// (Does the function work as advertised?)
//
// Test that the Add functions add correctly.
// Test that the CompareAndSwap functions actually
// do the comparison and the swap correctly.
//
// The loop over power-of-two values is meant to
// ensure that the operations apply to the full word size.
// The struct fields x.before and x.after check that the
// operations do not extend past the full word size.
const (
magic32 = 0xdedbeef
magic64 = 0xdeddeadbeefbeef
)
// Do the 64-bit functions panic? If so, don't bother testing.
var test64err = func() (err interface{}) {
defer func() {
err = recover()
}()
var x int64
AddInt64(&x, 1)
return nil
}()
func TestAddInt32(t *testing.T) {
var x struct {
before int32
i int32
after int32
}
x.before = magic32
x.after = magic32
var j int32
for delta := int32(1); delta+delta > delta; delta += delta {
k := AddInt32(&x.i, delta)
j += delta
if x.i != j || k != j {
t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k)
}
}
if x.before != magic32 || x.after != magic32 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32)
}
}
func TestAddUint32(t *testing.T) {
var x struct {
before uint32
i uint32
after uint32
}
x.before = magic32
x.after = magic32
var j uint32
for delta := uint32(1); delta+delta > delta; delta += delta {
k := AddUint32(&x.i, delta)
j += delta
if x.i != j || k != j {
t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k)
}
}
if x.before != magic32 || x.after != magic32 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32)
}
}
func TestAddInt64(t *testing.T) {
if test64err != nil {
t.Logf("Skipping 64-bit tests: %v", test64err)
return
}
var x struct {
before int64
i int64
after int64
}
x.before = magic64
x.after = magic64
var j int64
for delta := int64(1); delta+delta > delta; delta += delta {
k := AddInt64(&x.i, delta)
j += delta
if x.i != j || k != j {
t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k)
}
}
if x.before != magic64 || x.after != magic64 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, int64(magic64), int64(magic64))
}
}
func TestAddUint64(t *testing.T) {
if test64err != nil {
t.Logf("Skipping 64-bit tests: %v", test64err)
return
}
var x struct {
before uint64
i uint64
after uint64
}
x.before = magic64
x.after = magic64
var j uint64
for delta := uint64(1); delta+delta > delta; delta += delta {
k := AddUint64(&x.i, delta)
j += delta
if x.i != j || k != j {
t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k)
}
}
if x.before != magic64 || x.after != magic64 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, uint64(magic64), uint64(magic64))
}
}
func TestAddUintptr(t *testing.T) {
var x struct {
before uintptr
i uintptr
after uintptr
}
var m uint64 = magic64
magicptr := uintptr(m)
x.before = magicptr
x.after = magicptr
var j uintptr
for delta := uintptr(1); delta+delta > delta; delta += delta {
k := AddUintptr(&x.i, delta)
j += delta
if x.i != j || k != j {
t.Fatalf("delta=%d i=%d j=%d k=%d", delta, x.i, j, k)
}
}
if x.before != magicptr || x.after != magicptr {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr)
}
}
func TestCompareAndSwapInt32(t *testing.T) {
var x struct {
before int32
i int32
after int32
}
x.before = magic32
x.after = magic32
for val := int32(1); val+val > val; val += val {
x.i = val
if !CompareAndSwapInt32(&x.i, val, val+1) {
t.Errorf("should have swapped %#x %#x", val, val+1)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
x.i = val + 1
if CompareAndSwapInt32(&x.i, val, val+2) {
t.Errorf("should not have swapped %#x %#x", val, val+2)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
}
if x.before != magic32 || x.after != magic32 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32)
}
}
func TestCompareAndSwapUint32(t *testing.T) {
var x struct {
before uint32
i uint32
after uint32
}
x.before = magic32
x.after = magic32
for val := uint32(1); val+val > val; val += val {
x.i = val
if !CompareAndSwapUint32(&x.i, val, val+1) {
t.Errorf("should have swapped %#x %#x", val, val+1)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
x.i = val + 1
if CompareAndSwapUint32(&x.i, val, val+2) {
t.Errorf("should not have swapped %#x %#x", val, val+2)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
}
if x.before != magic32 || x.after != magic32 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32)
}
}
func TestCompareAndSwapInt64(t *testing.T) {
if test64err != nil {
t.Logf("Skipping 64-bit tests: %v", test64err)
return
}
var x struct {
before int64
i int64
after int64
}
x.before = magic64
x.after = magic64
for val := int64(1); val+val > val; val += val {
x.i = val
if !CompareAndSwapInt64(&x.i, val, val+1) {
t.Errorf("should have swapped %#x %#x", val, val+1)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
x.i = val + 1
if CompareAndSwapInt64(&x.i, val, val+2) {
t.Errorf("should not have swapped %#x %#x", val, val+2)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
}
if x.before != magic64 || x.after != magic64 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, uint64(magic64), uint64(magic64))
}
}
func TestCompareAndSwapUint64(t *testing.T) {
if test64err != nil {
t.Logf("Skipping 64-bit tests: %v", test64err)
return
}
var x struct {
before uint64
i uint64
after uint64
}
x.before = magic64
x.after = magic64
for val := uint64(1); val+val > val; val += val {
x.i = val
if !CompareAndSwapUint64(&x.i, val, val+1) {
t.Errorf("should have swapped %#x %#x", val, val+1)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
x.i = val + 1
if CompareAndSwapUint64(&x.i, val, val+2) {
t.Errorf("should not have swapped %#x %#x", val, val+2)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
}
if x.before != magic64 || x.after != magic64 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, uint64(magic64), uint64(magic64))
}
}
func TestCompareAndSwapUintptr(t *testing.T) {
var x struct {
before uintptr
i uintptr
after uintptr
}
var m uint64 = magic64
magicptr := uintptr(m)
x.before = magicptr
x.after = magicptr
for val := uintptr(1); val+val > val; val += val {
x.i = val
if !CompareAndSwapUintptr(&x.i, val, val+1) {
t.Errorf("should have swapped %#x %#x", val, val+1)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
x.i = val + 1
if CompareAndSwapUintptr(&x.i, val, val+2) {
t.Errorf("should not have swapped %#x %#x", val, val+2)
}
if x.i != val+1 {
t.Errorf("wrong x.i after swap: x.i=%#x val+1=%#x", x.i, val+1)
}
}
if x.before != magicptr || x.after != magicptr {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magicptr, magicptr)
}
}
func TestLoadInt32(t *testing.T) {
if runtime.GOARCH == "arm" && testing.Short() {
return /* TODO: broken on arm */
}
var x struct {
before int32
i int32
after int32
}
x.before = magic32
x.after = magic32
for delta := int32(1); delta+delta > delta; delta += delta {
k := LoadInt32(&x.i)
if k != x.i {
t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k)
}
x.i += delta
}
if x.before != magic32 || x.after != magic32 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32)
}
}
func TestLoadUint32(t *testing.T) {
if runtime.GOARCH == "arm" && testing.Short() {
return /* TODO: broken on arm */
}
var x struct {
before uint32
i uint32
after uint32
}
x.before = magic32
x.after = magic32
for delta := uint32(1); delta+delta > delta; delta += delta {
k := LoadUint32(&x.i)
if k != x.i {
t.Fatalf("delta=%d i=%d k=%d", delta, x.i, k)
}
x.i += delta
}
if x.before != magic32 || x.after != magic32 {
t.Fatalf("wrong magic: %#x _ %#x != %#x _ %#x", x.before, x.after, magic32, magic32)
}
}
// Tests of correct behavior, with contention.
// (Is the function atomic?)
//
// For each function, we write a "hammer" function that repeatedly
// uses the atomic operation to add 1 to a value. After running
// multiple hammers in parallel, check that we end with the correct
// total.
var hammer32 = []struct {
name string
f func(*uint32, int)
}{
{"AddInt32", hammerAddInt32},
{"AddUint32", hammerAddUint32},
{"AddUintptr", hammerAddUintptr32},
{"CompareAndSwapInt32", hammerCompareAndSwapInt32},
{"CompareAndSwapUint32", hammerCompareAndSwapUint32},
{"CompareAndSwapUintptr", hammerCompareAndSwapUintptr32},
}
func init() {
var v uint64 = 1 << 50
if uintptr(v) != 0 {
// 64-bit system; clear uintptr tests
hammer32[2].f = nil
hammer32[5].f = nil
}
}
func hammerAddInt32(uval *uint32, count int) {
val := (*int32)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
AddInt32(val, 1)
}
}
func hammerAddUint32(val *uint32, count int) {
for i := 0; i < count; i++ {
AddUint32(val, 1)
}
}
func hammerAddUintptr32(uval *uint32, count int) {
// only safe when uintptr is 32-bit.
// not called on 64-bit systems.
val := (*uintptr)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
AddUintptr(val, 1)
}
}
func hammerCompareAndSwapInt32(uval *uint32, count int) {
val := (*int32)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
for {
v := *val
if CompareAndSwapInt32(val, v, v+1) {
break
}
}
}
}
func hammerCompareAndSwapUint32(val *uint32, count int) {
for i := 0; i < count; i++ {
for {
v := *val
if CompareAndSwapUint32(val, v, v+1) {
break
}
}
}
}
func hammerCompareAndSwapUintptr32(uval *uint32, count int) {
// only safe when uintptr is 32-bit.
// not called on 64-bit systems.
val := (*uintptr)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
for {
v := *val
if CompareAndSwapUintptr(val, v, v+1) {
break
}
}
}
}
func TestHammer32(t *testing.T) {
const p = 4
n := 100000
if testing.Short() {
n = 1000
}
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(p))
for _, tt := range hammer32 {
if tt.f == nil {
continue
}
c := make(chan int)
var val uint32
for i := 0; i < p; i++ {
go func() {
tt.f(&val, n)
c <- 1
}()
}
for i := 0; i < p; i++ {
<-c
}
if val != uint32(n)*p {
t.Errorf("%s: val=%d want %d", tt.name, val, n*p)
}
}
}
var hammer64 = []struct {
name string
f func(*uint64, int)
}{
{"AddInt64", hammerAddInt64},
{"AddUint64", hammerAddUint64},
{"AddUintptr", hammerAddUintptr64},
{"CompareAndSwapInt64", hammerCompareAndSwapInt64},
{"CompareAndSwapUint64", hammerCompareAndSwapUint64},
{"CompareAndSwapUintptr", hammerCompareAndSwapUintptr64},
}
func init() {
var v uint64 = 1 << 50
if uintptr(v) == 0 {
// 32-bit system; clear uintptr tests
hammer64[2].f = nil
hammer64[5].f = nil
}
}
func hammerAddInt64(uval *uint64, count int) {
val := (*int64)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
AddInt64(val, 1)
}
}
func hammerAddUint64(val *uint64, count int) {
for i := 0; i < count; i++ {
AddUint64(val, 1)
}
}
func hammerAddUintptr64(uval *uint64, count int) {
// only safe when uintptr is 64-bit.
// not called on 32-bit systems.
val := (*uintptr)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
AddUintptr(val, 1)
}
}
func hammerCompareAndSwapInt64(uval *uint64, count int) {
val := (*int64)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
for {
v := *val
if CompareAndSwapInt64(val, v, v+1) {
break
}
}
}
}
func hammerCompareAndSwapUint64(val *uint64, count int) {
for i := 0; i < count; i++ {
for {
v := *val
if CompareAndSwapUint64(val, v, v+1) {
break
}
}
}
}
func hammerCompareAndSwapUintptr64(uval *uint64, count int) {
// only safe when uintptr is 64-bit.
// not called on 32-bit systems.
val := (*uintptr)(unsafe.Pointer(uval))
for i := 0; i < count; i++ {
for {
v := *val
if CompareAndSwapUintptr(val, v, v+1) {
break
}
}
}
}
func TestHammer64(t *testing.T) {
if test64err != nil {
t.Logf("Skipping 64-bit tests: %v", test64err)
return
}
const p = 4
n := 100000
if testing.Short() {
n = 1000
}
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(p))
for _, tt := range hammer64 {
if tt.f == nil {
continue
}
c := make(chan int)
var val uint64
for i := 0; i < p; i++ {
go func() {
tt.f(&val, n)
c <- 1
}()
}
for i := 0; i < p; i++ {
<-c
}
if val != uint64(n)*p {
t.Errorf("%s: val=%d want %d", tt.name, val, n*p)
}
}
}
func hammerLoadInt32(t *testing.T, uval *uint32) {
val := (*int32)(unsafe.Pointer(uval))
for {
v := LoadInt32(val)
vlo := v & ((1 << 16) - 1)
vhi := v >> 16
if vlo != vhi {
t.Fatalf("LoadInt32: %#x != %#x", vlo, vhi)
}
new := v + 1 + 1<<16
if vlo == 1e4 {
new = 0
}
if CompareAndSwapInt32(val, v, new) {
break
}
}
}
func hammerLoadUint32(t *testing.T, val *uint32) {
for {
v := LoadUint32(val)
vlo := v & ((1 << 16) - 1)
vhi := v >> 16
if vlo != vhi {
t.Fatalf("LoadUint32: %#x != %#x", vlo, vhi)
}
new := v + 1 + 1<<16
if vlo == 1e4 {
new = 0
}
if CompareAndSwapUint32(val, v, new) {
break
}
}
}
func TestHammerLoad(t *testing.T) {
if runtime.GOARCH == "arm" && testing.Short() {
return /* TODO: broken on arm */
}
tests := [...]func(*testing.T, *uint32){hammerLoadInt32, hammerLoadUint32}
n := 100000
if testing.Short() {
n = 10000
}
const procs = 8
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(procs))
for _, tt := range tests {
c := make(chan int)
var val uint32
for p := 0; p < procs; p++ {
go func() {
for i := 0; i < n; i++ {
tt(t, &val)
}
c <- 1
}()
}
for p := 0; p < procs; p++ {
<-c
}
}
}