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
diff --git a/src/lib/exvar.go b/src/lib/exvar.go
index 88f36b7..38fd2c1 100644
--- a/src/lib/exvar.go
+++ b/src/lib/exvar.go
@@ -8,7 +8,6 @@
import (
"fmt";
- "sync";
)
// If mismatched names are used (e.g. calling IncrementInt on a mapVar), the
@@ -49,112 +48,139 @@
// - string-valued vars
// - dynamic lookup vars (via chan?)
-// Global state.
-var (
- mutex sync.Mutex;
- vars = make(map[string] exVar);
+type exVars struct {
+ vars map[string] exVar;
// 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.
-// Callers should already be holding the mutex.
-func getOrInitIntVar(name string) *intVar {
- if v, ok := vars[name]; ok {
+func (state *exVars) getOrInitIntVar(name string) *intVar {
+ if v, ok := state.vars[name]; ok {
// Existing var
if iv, ok := v.(*intVar); ok {
return iv
}
// Type mismatch.
- return getOrInitIntVar(mismatchedInt)
+ return state.getOrInitIntVar(mismatchedInt)
}
// New var
iv := new(intVar);
- vars[name] = iv;
+ state.vars[name] = iv;
return iv
}
// getOrInitMapVar either gets or initializes a mapVar called name.
-// Callers should already be holding the mutex.
-func getOrInitMapVar(name string) *mapVar {
- if v, ok := vars[name]; ok {
+func (state *exVars) getOrInitMapVar(name string) *mapVar {
+ if v, ok := state.vars[name]; ok {
// Existing var
if mv, ok := v.(*mapVar); ok {
return mv
}
// Type mismatch.
- return getOrInitMapVar(mismatchedMap)
+ return state.getOrInitMapVar(mismatchedMap)
}
// New var
var m mapVar = make(map[string] int);
- vars[name] = &m;
+ state.vars[name] = &m;
return &m
}
// IncrementInt adds inc to the integer-valued var called name.
func IncrementInt(name string, inc int) {
- mutex.Lock();
- defer mutex.Unlock();
-
- *getOrInitIntVar(name) += inc
+ workSync(func(state *exVars) {
+ *state.getOrInitIntVar(name) += inc
+ })
}
-// IncrementMap adds inc to the keyed value in the map-valued var called name.
-func IncrementMap(name string, key string, inc int) {
- mutex.Lock();
- defer mutex.Unlock();
-
- mv := getOrInitMapVar(name);
- // TODO(dsymonds): Change this to just mv[key] when bug143 is fixed.
- if v, ok := (*mv)[key]; ok {
- mv[key] += inc
- } else {
- mv[key] = inc
- }
+// IncrementMapInt adds inc to the keyed value in the map-valued var called name.
+func IncrementMapInt(name string, key string, inc int) {
+ workSync(func(state *exVars) {
+ mv := state.getOrInitMapVar(name);
+ // TODO(dsymonds): Change this to just mv[key] when bug143 is fixed.
+ if v, ok := (*mv)[key]; ok {
+ mv[key] += inc
+ } else {
+ mv[key] = inc
+ }
+ })
}
// SetInt sets the integer-valued var called name to value.
func SetInt(name string, value int) {
- mutex.Lock();
- defer mutex.Unlock();
-
- *getOrInitIntVar(name) = value
+ workSync(func(state *exVars) {
+ *state.getOrInitIntVar(name) = value
+ })
}
-// SetMap sets the keyed value in the map-valued var called name.
-func SetMap(name string, key string, value int) {
- mutex.Lock();
- defer mutex.Unlock();
-
- getOrInitMapVar(name)[key] = value
+// SetMapInt sets the keyed value in the map-valued var called name.
+func SetMapInt(name string, key string, value int) {
+ workSync(func(state *exVars) {
+ state.getOrInitMapVar(name)[key] = value
+ })
}
// GetInt retrieves an integer-valued var called name.
func GetInt(name string) int {
- mutex.Lock();
- defer mutex.Unlock();
-
- return *getOrInitIntVar(name)
+ var i int;
+ workSync(func(state *exVars) {
+ i = *state.getOrInitIntVar(name)
+ });
+ return i
}
-// GetMap retrieves the keyed value for a map-valued var called name.
-func GetMap(name string, key string) int {
- mutex.Lock();
- defer mutex.Unlock();
-
- // TODO(dsymonds): Change this to just getOrInitMapVar(name)[key] when
- // bug143 is fixed.
- x, ok := (*getOrInitMapVar(name))[key];
- return x
+// GetMapInt retrieves the keyed value for a map-valued var called name.
+func GetMapInt(name string, key string) int {
+ var i int;
+ var ok bool;
+ workSync(func(state *exVars) {
+ // TODO(dsymonds): Change this to just getOrInitMapVar(name)[key] when
+ // bug143 is fixed.
+ i, ok = (*state.getOrInitMapVar(name))[key];
+ });
+ return i
}
// String produces a string of all the vars in textual format.
func String() string {
- mutex.Lock();
- defer mutex.Unlock();
-
s := "";
- for name, value := range vars {
- s += fmt.Sprintln(name, value)
- }
+ workSync(func(state *exVars) {
+ for name, value := range state.vars {
+ s += fmt.Sprintln(name, value)
+ }
+ });
return s
}