mirror of
https://github.com/golang/go
synced 2024-11-12 09:50:21 -07:00
expvar: sort maps, fix race
It's pretty distracting to use expvar with the output of both the top-level map and map values jumping around randomly. Also fixes a potential race where multiple clients trying to increment a map int or float key at the same time could lose updates. R=golang-codereviews, couchmoney CC=golang-codereviews https://golang.org/cl/54320043
This commit is contained in:
parent
6592aeb8f3
commit
cfb9cf0f06
@ -29,6 +29,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@ -40,8 +41,8 @@ type Var interface {
|
|||||||
|
|
||||||
// Int is a 64-bit integer variable that satisfies the Var interface.
|
// Int is a 64-bit integer variable that satisfies the Var interface.
|
||||||
type Int struct {
|
type Int struct {
|
||||||
i int64
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
i int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Int) String() string {
|
func (v *Int) String() string {
|
||||||
@ -64,8 +65,8 @@ func (v *Int) Set(value int64) {
|
|||||||
|
|
||||||
// Float is a 64-bit float variable that satisfies the Var interface.
|
// Float is a 64-bit float variable that satisfies the Var interface.
|
||||||
type Float struct {
|
type Float struct {
|
||||||
f float64
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
f float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Float) String() string {
|
func (v *Float) String() string {
|
||||||
@ -90,8 +91,9 @@ func (v *Float) Set(value float64) {
|
|||||||
|
|
||||||
// Map is a string-to-Var map variable that satisfies the Var interface.
|
// Map is a string-to-Var map variable that satisfies the Var interface.
|
||||||
type Map struct {
|
type Map struct {
|
||||||
m map[string]Var
|
mu sync.RWMutex
|
||||||
mu sync.RWMutex
|
m map[string]Var
|
||||||
|
keys []string // sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyValue represents a single entry in a Map.
|
// KeyValue represents a single entry in a Map.
|
||||||
@ -106,13 +108,13 @@ func (v *Map) String() string {
|
|||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
fmt.Fprintf(&b, "{")
|
fmt.Fprintf(&b, "{")
|
||||||
first := true
|
first := true
|
||||||
for key, val := range v.m {
|
v.Do(func(kv KeyValue) {
|
||||||
if !first {
|
if !first {
|
||||||
fmt.Fprintf(&b, ", ")
|
fmt.Fprintf(&b, ", ")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&b, "\"%s\": %v", key, val)
|
fmt.Fprintf(&b, "\"%s\": %v", kv.Key, kv.Value)
|
||||||
first = false
|
first = false
|
||||||
}
|
})
|
||||||
fmt.Fprintf(&b, "}")
|
fmt.Fprintf(&b, "}")
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
@ -122,6 +124,20 @@ func (v *Map) Init() *Map {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateKeys updates the sorted list of keys in v.keys.
|
||||||
|
// must be called with v.mu held.
|
||||||
|
func (v *Map) updateKeys() {
|
||||||
|
if len(v.m) == len(v.keys) {
|
||||||
|
// No new key.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.keys = v.keys[:0]
|
||||||
|
for k := range v.m {
|
||||||
|
v.keys = append(v.keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(v.keys)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Map) Get(key string) Var {
|
func (v *Map) Get(key string) Var {
|
||||||
v.mu.RLock()
|
v.mu.RLock()
|
||||||
defer v.mu.RUnlock()
|
defer v.mu.RUnlock()
|
||||||
@ -132,6 +148,7 @@ func (v *Map) Set(key string, av Var) {
|
|||||||
v.mu.Lock()
|
v.mu.Lock()
|
||||||
defer v.mu.Unlock()
|
defer v.mu.Unlock()
|
||||||
v.m[key] = av
|
v.m[key] = av
|
||||||
|
v.updateKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Map) Add(key string, delta int64) {
|
func (v *Map) Add(key string, delta int64) {
|
||||||
@ -141,9 +158,11 @@ func (v *Map) Add(key string, delta int64) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
// check again under the write lock
|
// check again under the write lock
|
||||||
v.mu.Lock()
|
v.mu.Lock()
|
||||||
if _, ok = v.m[key]; !ok {
|
av, ok = v.m[key]
|
||||||
|
if !ok {
|
||||||
av = new(Int)
|
av = new(Int)
|
||||||
v.m[key] = av
|
v.m[key] = av
|
||||||
|
v.updateKeys()
|
||||||
}
|
}
|
||||||
v.mu.Unlock()
|
v.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -162,9 +181,11 @@ func (v *Map) AddFloat(key string, delta float64) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
// check again under the write lock
|
// check again under the write lock
|
||||||
v.mu.Lock()
|
v.mu.Lock()
|
||||||
if _, ok = v.m[key]; !ok {
|
av, ok = v.m[key]
|
||||||
|
if !ok {
|
||||||
av = new(Float)
|
av = new(Float)
|
||||||
v.m[key] = av
|
v.m[key] = av
|
||||||
|
v.updateKeys()
|
||||||
}
|
}
|
||||||
v.mu.Unlock()
|
v.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -181,15 +202,15 @@ func (v *Map) AddFloat(key string, delta float64) {
|
|||||||
func (v *Map) Do(f func(KeyValue)) {
|
func (v *Map) Do(f func(KeyValue)) {
|
||||||
v.mu.RLock()
|
v.mu.RLock()
|
||||||
defer v.mu.RUnlock()
|
defer v.mu.RUnlock()
|
||||||
for k, v := range v.m {
|
for _, k := range v.keys {
|
||||||
f(KeyValue{k, v})
|
f(KeyValue{k, v.m[k]})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String is a string variable, and satisfies the Var interface.
|
// String is a string variable, and satisfies the Var interface.
|
||||||
type String struct {
|
type String struct {
|
||||||
s string
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
s string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *String) String() string {
|
func (v *String) String() string {
|
||||||
@ -215,8 +236,9 @@ func (f Func) String() string {
|
|||||||
|
|
||||||
// All published variables.
|
// All published variables.
|
||||||
var (
|
var (
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
vars map[string]Var = make(map[string]Var)
|
vars = make(map[string]Var)
|
||||||
|
varKeys []string // sorted
|
||||||
)
|
)
|
||||||
|
|
||||||
// Publish declares a named exported variable. This should be called from a
|
// Publish declares a named exported variable. This should be called from a
|
||||||
@ -229,6 +251,8 @@ func Publish(name string, v Var) {
|
|||||||
log.Panicln("Reuse of exported var name:", name)
|
log.Panicln("Reuse of exported var name:", name)
|
||||||
}
|
}
|
||||||
vars[name] = v
|
vars[name] = v
|
||||||
|
varKeys = append(varKeys, name)
|
||||||
|
sort.Strings(varKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves a named exported variable.
|
// Get retrieves a named exported variable.
|
||||||
@ -270,8 +294,8 @@ func NewString(name string) *String {
|
|||||||
func Do(f func(KeyValue)) {
|
func Do(f func(KeyValue)) {
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
defer mutex.RUnlock()
|
defer mutex.RUnlock()
|
||||||
for k, v := range vars {
|
for _, k := range varKeys {
|
||||||
f(KeyValue{k, v})
|
f(KeyValue{k, vars[k]})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
package expvar
|
package expvar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ func RemoveAll() {
|
|||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
vars = make(map[string]Var)
|
vars = make(map[string]Var)
|
||||||
|
varKeys = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInt(t *testing.T) {
|
func TestInt(t *testing.T) {
|
||||||
@ -139,3 +143,25 @@ func TestFunc(t *testing.T) {
|
|||||||
t.Errorf(`f.String() = %q, want %q`, s, exp)
|
t.Errorf(`f.String() = %q, want %q`, s, exp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandler(t *testing.T) {
|
||||||
|
RemoveAll()
|
||||||
|
m := NewMap("map1")
|
||||||
|
m.Add("a", 1)
|
||||||
|
m.Add("z", 2)
|
||||||
|
m2 := NewMap("map2")
|
||||||
|
for i := 0; i < 9; i++ {
|
||||||
|
m2.Add(strconv.Itoa(i), int64(i))
|
||||||
|
}
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
rr.Body = new(bytes.Buffer)
|
||||||
|
expvarHandler(rr, nil)
|
||||||
|
want := `{
|
||||||
|
"map1": {"a": 1, "z": 2},
|
||||||
|
"map2": {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if got := rr.Body.String(); got != want {
|
||||||
|
t.Errorf("HTTP handler wrote:\n%s\nWant:\n%s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user