blob: fd90451dd83f6afdce6da4ca95b79a38519de4e8 [file] [log] [blame]
// Copyright 2014 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 (
"math/rand"
"runtime"
"sync"
. "sync/atomic"
"testing"
"time"
)
func TestValue(t *testing.T) {
var v Value
if v.Load() != nil {
t.Fatal("initial Value is not nil")
}
v.Store(42)
x := v.Load()
if xx, ok := x.(int); !ok || xx != 42 {
t.Fatalf("wrong value: got %+v, want 42", x)
}
v.Store(84)
x = v.Load()
if xx, ok := x.(int); !ok || xx != 84 {
t.Fatalf("wrong value: got %+v, want 84", x)
}
}
func TestValueLarge(t *testing.T) {
var v Value
v.Store("foo")
x := v.Load()
if xx, ok := x.(string); !ok || xx != "foo" {
t.Fatalf("wrong value: got %+v, want foo", x)
}
v.Store("barbaz")
x = v.Load()
if xx, ok := x.(string); !ok || xx != "barbaz" {
t.Fatalf("wrong value: got %+v, want barbaz", x)
}
}
func TestValuePanic(t *testing.T) {
const nilErr = "sync/atomic: store of nil value into Value"
const badErr = "sync/atomic: store of inconsistently typed value into Value"
var v Value
func() {
defer func() {
err := recover()
if err != nilErr {
t.Fatalf("inconsistent store panic: got '%v', want '%v'", err, nilErr)
}
}()
v.Store(nil)
}()
v.Store(42)
func() {
defer func() {
err := recover()
if err != badErr {
t.Fatalf("inconsistent store panic: got '%v', want '%v'", err, badErr)
}
}()
v.Store("foo")
}()
func() {
defer func() {
err := recover()
if err != nilErr {
t.Fatalf("inconsistent store panic: got '%v', want '%v'", err, nilErr)
}
}()
v.Store(nil)
}()
}
func TestValueConcurrent(t *testing.T) {
tests := [][]interface{}{
{uint16(0), ^uint16(0), uint16(1 + 2<<8), uint16(3 + 4<<8)},
{uint32(0), ^uint32(0), uint32(1 + 2<<16), uint32(3 + 4<<16)},
{uint64(0), ^uint64(0), uint64(1 + 2<<32), uint64(3 + 4<<32)},
{complex(0, 0), complex(1, 2), complex(3, 4), complex(5, 6)},
}
p := 4 * runtime.GOMAXPROCS(0)
N := int(1e5)
if testing.Short() {
p /= 2
N = 1e3
}
for _, test := range tests {
var v Value
done := make(chan bool)
for i := 0; i < p; i++ {
go func() {
r := rand.New(rand.NewSource(rand.Int63()))
loop:
for j := 0; j < N; j++ {
x := test[r.Intn(len(test))]
v.Store(x)
x = v.Load()
for _, x1 := range test {
if x == x1 {
continue loop
}
}
t.Logf("loaded unexpected value %+v, want %+v", x, test)
done <- false
}
done <- true
}()
}
for i := 0; i < p; i++ {
if !<-done {
t.FailNow()
}
}
}
}
func BenchmarkValueRead(b *testing.B) {
var v Value
v.Store(new(int))
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
x := v.Load().(*int)
if *x != 0 {
b.Fatalf("wrong value: got %v, want 0", *x)
}
}
})
}
// The following example shows how to use Value for periodic program config updates
// and propagation of the changes to worker goroutines.
func ExampleValue_config() {
var config Value // holds current server configuration
// Create initial config value and store into config.
config.Store(loadConfig())
go func() {
// Reload config every 10 seconds
// and update config value with the new version.
for {
time.Sleep(10 * time.Second)
config.Store(loadConfig())
}
}()
// Create worker goroutines that handle incoming requests
// using the latest config value.
for i := 0; i < 10; i++ {
go func() {
for r := range requests() {
c := config.Load()
// Handle request r using config c.
_, _ = r, c
}
}()
}
}
func loadConfig() map[string]string {
return make(map[string]string)
}
func requests() chan int {
return make(chan int)
}
// The following example shows how to maintain a scalable frequently read,
// but infrequently updated data structure using copy-on-write idiom.
func ExampleValue_readMostly() {
type Map map[string]string
var m Value
m.Store(make(Map))
var mu sync.Mutex // used only by writers
// read function can be used to read the data without further synchronization
read := func(key string) (val string) {
m1 := m.Load().(Map)
return m1[key]
}
// insert function can be used to update the data without further synchronization
insert := func(key, val string) {
mu.Lock() // synchronize with other potential writers
defer mu.Unlock()
m1 := m.Load().(Map) // load current value of the data structure
m2 := make(Map) // create a new value
for k, v := range m1 {
m2[k] = v // copy all data from the current object to the new one
}
m2[key] = val // do the update that we need
m.Store(m2) // atomically replace the current object with the new one
// At this point all new readers start working with the new version.
// The old version will be garbage collected once the existing readers
// (if any) are done with it.
}
_, _ = read, insert
}