Commit 5cb6843a authored by David Symonds's avatar David Symonds

Change exvar to use a goroutine channel worker instead of a mutex for synchronisation.

Also it should be more testable, as there's less global state.

R=r
APPROVED=r
DELTA=113  (38 added, 12 deleted, 63 changed)
OCL=27653
CL=27694
parent 9c456283
...@@ -99,7 +99,7 @@ test: test.files ...@@ -99,7 +99,7 @@ test: test.files
bignum.6: fmt.dirinstall bignum.6: fmt.dirinstall
bufio.6: io.dirinstall os.dirinstall bufio.6: io.dirinstall os.dirinstall
exec.6: os.dirinstall strings.install exec.6: os.dirinstall strings.install
exvar.6: fmt.dirinstall sync.dirinstall exvar.6: fmt.dirinstall
flag.6: fmt.dirinstall os.dirinstall strconv.dirinstall flag.6: fmt.dirinstall os.dirinstall strconv.dirinstall
log.6: fmt.dirinstall io.dirinstall os.dirinstall time.dirinstall log.6: fmt.dirinstall io.dirinstall os.dirinstall time.dirinstall
path.6: io.dirinstall path.6: io.dirinstall
......
...@@ -8,7 +8,6 @@ package exvar ...@@ -8,7 +8,6 @@ package exvar
import ( import (
"fmt"; "fmt";
"sync";
) )
// If mismatched names are used (e.g. calling IncrementInt on a mapVar), the // If mismatched names are used (e.g. calling IncrementInt on a mapVar), the
...@@ -49,112 +48,139 @@ func (m mapVar) String() string { ...@@ -49,112 +48,139 @@ func (m mapVar) String() string {
// - string-valued vars // - string-valued vars
// - dynamic lookup vars (via chan?) // - dynamic lookup vars (via chan?)
// Global state. type exVars struct {
var ( vars map[string] exVar;
mutex sync.Mutex;
vars = make(map[string] exVar);
// TODO(dsymonds): docstrings // TODO(dsymonds): docstrings
) }
// Singleton worker goroutine.
// Functions needing access to the global state have to pass a closure to the
// worker channel, which is read by a single workerFunc running in a goroutine.
// Nil values are silently ignored, so you can send nil to the worker channel
// after the closure if you want to block until your work is done. This risks
// blocking you, though. The workSync function wraps this as a convenience.
type workFunction func(*exVars);
// The main worker function that runs in a goroutine.
// It never ends in normal operation.
func startWorkerFunc() <-chan workFunction {
ch := make(chan workFunction);
state := &exVars{ make(map[string] exVar) };
go func() {
for f := range ch {
if f != nil {
f(state)
}
}
}();
return ch
}
var worker = startWorkerFunc();
// workSync will enqueue the given workFunction and wait for it to finish.
func workSync(f workFunction) {
worker <- f;
worker <- nil // will only be sent after f() completes.
}
// getOrInitIntVar either gets or initializes an intVar called name. // getOrInitIntVar either gets or initializes an intVar called name.
// Callers should already be holding the mutex. func (state *exVars) getOrInitIntVar(name string) *intVar {
func getOrInitIntVar(name string) *intVar { if v, ok := state.vars[name]; ok {
if v, ok := vars[name]; ok {
// Existing var // Existing var
if iv, ok := v.(*intVar); ok { if iv, ok := v.(*intVar); ok {
return iv return iv
} }
// Type mismatch. // Type mismatch.
return getOrInitIntVar(mismatchedInt) return state.getOrInitIntVar(mismatchedInt)
} }
// New var // New var
iv := new(intVar); iv := new(intVar);
vars[name] = iv; state.vars[name] = iv;
return iv return iv
} }
// getOrInitMapVar either gets or initializes a mapVar called name. // getOrInitMapVar either gets or initializes a mapVar called name.
// Callers should already be holding the mutex. func (state *exVars) getOrInitMapVar(name string) *mapVar {
func getOrInitMapVar(name string) *mapVar { if v, ok := state.vars[name]; ok {
if v, ok := vars[name]; ok {
// Existing var // Existing var
if mv, ok := v.(*mapVar); ok { if mv, ok := v.(*mapVar); ok {
return mv return mv
} }
// Type mismatch. // Type mismatch.
return getOrInitMapVar(mismatchedMap) return state.getOrInitMapVar(mismatchedMap)
} }
// New var // New var
var m mapVar = make(map[string] int); var m mapVar = make(map[string] int);
vars[name] = &m; state.vars[name] = &m;
return &m return &m
} }
// IncrementInt adds inc to the integer-valued var called name. // IncrementInt adds inc to the integer-valued var called name.
func IncrementInt(name string, inc int) { func IncrementInt(name string, inc int) {
mutex.Lock(); workSync(func(state *exVars) {
defer mutex.Unlock(); *state.getOrInitIntVar(name) += inc
})
*getOrInitIntVar(name) += inc
} }
// IncrementMap adds inc to the keyed value in the map-valued var called name. // IncrementMapInt adds inc to the keyed value in the map-valued var called name.
func IncrementMap(name string, key string, inc int) { func IncrementMapInt(name string, key string, inc int) {
mutex.Lock(); workSync(func(state *exVars) {
defer mutex.Unlock(); mv := state.getOrInitMapVar(name);
// TODO(dsymonds): Change this to just mv[key] when bug143 is fixed.
mv := getOrInitMapVar(name); if v, ok := (*mv)[key]; ok {
// TODO(dsymonds): Change this to just mv[key] when bug143 is fixed. mv[key] += inc
if v, ok := (*mv)[key]; ok { } else {
mv[key] += inc mv[key] = inc
} else { }
mv[key] = inc })
}
} }
// SetInt sets the integer-valued var called name to value. // SetInt sets the integer-valued var called name to value.
func SetInt(name string, value int) { func SetInt(name string, value int) {
mutex.Lock(); workSync(func(state *exVars) {
defer mutex.Unlock(); *state.getOrInitIntVar(name) = value
})
*getOrInitIntVar(name) = value
} }
// SetMap sets the keyed value in the map-valued var called name. // SetMapInt sets the keyed value in the map-valued var called name.
func SetMap(name string, key string, value int) { func SetMapInt(name string, key string, value int) {
mutex.Lock(); workSync(func(state *exVars) {
defer mutex.Unlock(); state.getOrInitMapVar(name)[key] = value
})
getOrInitMapVar(name)[key] = value
} }
// GetInt retrieves an integer-valued var called name. // GetInt retrieves an integer-valued var called name.
func GetInt(name string) int { func GetInt(name string) int {
mutex.Lock(); var i int;
defer mutex.Unlock(); workSync(func(state *exVars) {
i = *state.getOrInitIntVar(name)
return *getOrInitIntVar(name) });
return i
} }
// GetMap retrieves the keyed value for a map-valued var called name. // GetMapInt retrieves the keyed value for a map-valued var called name.
func GetMap(name string, key string) int { func GetMapInt(name string, key string) int {
mutex.Lock(); var i int;
defer mutex.Unlock(); var ok bool;
workSync(func(state *exVars) {
// TODO(dsymonds): Change this to just getOrInitMapVar(name)[key] when // TODO(dsymonds): Change this to just getOrInitMapVar(name)[key] when
// bug143 is fixed. // bug143 is fixed.
x, ok := (*getOrInitMapVar(name))[key]; i, ok = (*state.getOrInitMapVar(name))[key];
return x });
return i
} }
// String produces a string of all the vars in textual format. // String produces a string of all the vars in textual format.
func String() string { func String() string {
mutex.Lock();
defer mutex.Unlock();
s := ""; s := "";
for name, value := range vars { workSync(func(state *exVars) {
s += fmt.Sprintln(name, value) for name, value := range state.vars {
} s += fmt.Sprintln(name, value)
}
});
return s return s
} }
...@@ -34,33 +34,33 @@ func TestSimpleCounter(t *testing.T) { ...@@ -34,33 +34,33 @@ func TestSimpleCounter(t *testing.T) {
func TestMismatchedCounters(t *testing.T) { func TestMismatchedCounters(t *testing.T) {
// Make sure some vars exist. // Make sure some vars exist.
GetInt("requests"); GetInt("requests");
GetMap("colours", "red"); GetMapInt("colours", "red");
IncrementInt("colours", 1); IncrementInt("colours", 1);
if x := GetInt("x-mismatched-int"); x != 1 { if x := GetInt("x-mismatched-int"); x != 1 {
t.Errorf("GetInt('x-mismatched-int') = %v, want 1", x) t.Errorf("GetInt('x-mismatched-int') = %v, want 1", x)
} }
IncrementMap("requests", "orange", 1); IncrementMapInt("requests", "orange", 1);
if x := GetMap("x-mismatched-map", "orange"); x != 1 { if x := GetMapInt("x-mismatched-map", "orange"); x != 1 {
t.Errorf("GetMap('x-mismatched-int', 'orange') = %v, want 1", x) t.Errorf("GetMapInt('x-mismatched-int', 'orange') = %v, want 1", x)
} }
} }
func TestMapCounter(t *testing.T) { func TestMapCounter(t *testing.T) {
// Unknown exvar should be zero. // Unknown exvar should be zero.
if x := GetMap("colours", "red"); x != 0 { if x := GetMapInt("colours", "red"); x != 0 {
t.Errorf("GetMap(non, existent) = %v, want 0", x) t.Errorf("GetMapInt(non, existent) = %v, want 0", x)
} }
IncrementMap("colours", "red", 1); IncrementMapInt("colours", "red", 1);
IncrementMap("colours", "red", 2); IncrementMapInt("colours", "red", 2);
IncrementMap("colours", "blue", 4); IncrementMapInt("colours", "blue", 4);
if x := GetMap("colours", "red"); x != 3 { if x := GetMapInt("colours", "red"); x != 3 {
t.Errorf("GetMap('colours', 'red') = %v, want 3", x) t.Errorf("GetMapInt('colours', 'red') = %v, want 3", x)
} }
if x := GetMap("colours", "blue"); x != 4 { if x := GetMapInt("colours", "blue"); x != 4 {
t.Errorf("GetMap('colours', 'blue') = %v, want 4", x) t.Errorf("GetMapInt('colours', 'blue') = %v, want 4", x)
} }
// TODO(dsymonds): Test String() // TODO(dsymonds): Test String()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment