exvar.go 5.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
// Copyright 2009 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.

// The exvar package provides a standardized interface to public variables,
// such as operation counters in servers.
package exvar

import (
	"fmt";
)

13 14 15 16 17 18 19 20 21
// If mismatched names are used (e.g. calling IncrementInt on a mapVar), the
// var name is silently mapped to these. We will consider variables starting
// with reservedPrefix to be reserved by this package, and so we avoid the
// possibility of a user doing IncrementInt("x-mismatched-map", 1).
// TODO(dsymonds): Enforce this.
const (
	reservedPrefix = "x-";
	mismatchedInt = reservedPrefix + "mismatched-int";
	mismatchedMap = reservedPrefix + "mismatched-map";
22
	mismatchedStr = reservedPrefix + "mismatched-str";
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
)

// exVar is an abstract type for all exported variables.
type exVar interface {
	String() string;
}

// intVar is an integer variable, and satisfies the exVar interface.
type intVar int;

func (i intVar) String() string {
	return fmt.Sprint(int(i))
}

// mapVar is a map variable, and satisfies the exVar interface.
type mapVar map[string] int;

func (m mapVar) String() string {
	s := "map:x";  // TODO(dsymonds): the 'x' should be user-specified!
	for k, v := range m {
		s += fmt.Sprintf(" %s:%v", k, v)
	}
	return s
}

48 49 50 51 52 53 54
// strVar is a string variable, and satisfies the exVar interface.
type strVar string;

func (s strVar) String() string {
	return fmt.Sprintf("%q", s)
}

55 56 57
// TODO(dsymonds):
// - dynamic lookup vars (via chan?)

58 59
type exVars struct {
	vars map[string] exVar;
60
	// TODO(dsymonds): docstrings
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
}

// 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.
}
96

97
// getOrInitIntVar either gets or initializes an intVar called name.
98 99
func (state *exVars) getOrInitIntVar(name string) *intVar {
	if v, ok := state.vars[name]; ok {
100 101 102 103 104
		// Existing var
		if iv, ok := v.(*intVar); ok {
			return iv
		}
		// Type mismatch.
105
		return state.getOrInitIntVar(mismatchedInt)
106 107 108
	}
	// New var
	iv := new(intVar);
109
	state.vars[name] = iv;
110 111 112 113
	return iv
}

// getOrInitMapVar either gets or initializes a mapVar called name.
114 115
func (state *exVars) getOrInitMapVar(name string) *mapVar {
	if v, ok := state.vars[name]; ok {
116 117 118 119 120
		// Existing var
		if mv, ok := v.(*mapVar); ok {
			return mv
		}
		// Type mismatch.
121
		return state.getOrInitMapVar(mismatchedMap)
122 123 124
	}
	// New var
	var m mapVar = make(map[string] int);
125
	state.vars[name] = &m;
126 127 128
	return &m
}

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
// getOrInitStrVar either gets or initializes a strVar called name.
func (state *exVars) getOrInitStrVar(name string) *strVar {
	if v, ok := state.vars[name]; ok {
		// Existing var
		if mv, ok := v.(*strVar); ok {
			return mv
		}
		// Type mismatch.
		return state.getOrInitStrVar(mismatchedStr)
	}
	// New var
	sv := new(strVar);
	state.vars[name] = sv;
	return sv
}

145 146
// IncrementInt adds inc to the integer-valued var called name.
func IncrementInt(name string, inc int) {
147 148 149
	workSync(func(state *exVars) {
		*state.getOrInitIntVar(name) += inc
	})
150 151
}

152 153 154 155
// 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);
156
		if v, ok := mv[key]; ok {
157 158 159 160 161
			mv[key] += inc
		} else {
			mv[key] = inc
		}
	})
162 163
}

164 165
// SetInt sets the integer-valued var called name to value.
func SetInt(name string, value int) {
166 167 168
	workSync(func(state *exVars) {
		*state.getOrInitIntVar(name) = value
	})
169 170
}

171 172 173 174 175
// 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
	})
176 177
}

178 179 180 181 182 183 184
// SetStr sets the string-valued var called name to value.
func SetStr(name string, value string) {
	workSync(func(state *exVars) {
		*state.getOrInitStrVar(name) = value
	})
}

185 186
// GetInt retrieves an integer-valued var called name.
func GetInt(name string) int {
187 188 189 190 191
	var i int;
	workSync(func(state *exVars) {
		i = *state.getOrInitIntVar(name)
	});
	return i
192 193
}

194 195 196 197 198
// 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) {
199
		i, ok = state.getOrInitMapVar(name)[key]
200 201
	});
	return i
202
}
203

204 205 206 207 208 209 210 211 212
// GetStr retrieves a string-valued var called name.
func GetStr(name string) string {
	var s string;
	workSync(func(state *exVars) {
		s = *state.getOrInitStrVar(name)
	});
	return s
}

213 214 215
// String produces a string of all the vars in textual format.
func String() string {
	s := "";
216 217 218 219 220
	workSync(func(state *exVars) {
		for name, value := range state.vars {
			s += fmt.Sprintln(name, value)
		}
	});
221 222
	return s
}