Skip to content

Instantly share code, notes, and snippets.

@imjasonh
Last active October 28, 2021 06:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save imjasonh/fa3c0dd0a8c45d7a605387b9265cac98 to your computer and use it in GitHub Desktop.
Save imjasonh/fa3c0dd0a8c45d7a605387b9265cac98 to your computer and use it in GitHub Desktop.
Value
package val
import (
"errors"
"time"
)
// ErrTimeout is returned by GetWithTimeout if no value is available before the
// specified timeout.
var ErrTimeout = errors.New("timed out waiting for value")
// Value holds a string value and provides it in a concurrency-safe manner.
type Value interface {
// Get returns the current value, blocking until a value is available.
//
// Get will never return an empty string.
Get() string
// GetWithTimeout returns the current value, or returns ErrTimeout if the
// specified timeout is exceeded.
GetWithTimeout(time.Duration) (string, error)
// Set sets the current value.
Set(string)
// Clear clears the current value.
Clear(string)
}
// NewValue returns a Value that holds a string value and provides it in a
// concurrency-safe manner.
func NewValue() Value {
v := value{
newValCh: make(chan string),
getValCh: make(chan string),
clearCh: make(chan string),
}
go v.mux()
return v
}
type value struct {
newValCh chan string
getValCh chan string
clearCh chan string
}
func (v value) mux() {
var cur string
for {
if cur == "" { // don't send an empty string.
select {
case cur = <-v.newValCh: // got a new value.
case <-v.clearCh: // clearing should not block while cur == ""
}
}
select {
case cur = <-v.newValCh: // set the current value.
case v.getValCh <- cur: // send the current value.
case c := <-v.clearCh: // request to clear the value.
if c == cur { // only clear if it matches the current value.
cur = ""
}
}
}
}
func (v value) Get() string {
return <-v.getValCh // blocks until there is a value to send.
}
func (v value) GetWithTimeout(d time.Duration) (string, error) {
select {
case <-time.After(d): // wait d, then return error
return "", ErrTimeout
case cur := <-v.getValCh:
return cur, nil
}
}
func (v value) Set(s string) {
v.newValCh <- s // blocks until it can send.
}
func (v value) Clear(s string) {
v.clearCh <- s // blocks until it can send.
}
package val
import (
"testing"
"time"
)
func TestValue(t *testing.T) {
v := NewValue()
v.Set("foo")
if got, want := v.Get(), "foo"; got != want {
t.Errorf("Get() got %q, want %q", got, want)
}
v.Set("bar")
if got, want := v.Get(), "bar"; got != want {
t.Errorf("Get() got %q, want %q", got, want)
}
if got, want := v.Get(), "bar"; got != want {
t.Errorf("Get() got %q, want %q", got, want)
}
v.Clear("other") // has no effect, does not block.
if got, want := v.Get(), "bar"; got != want {
t.Errorf("Get() got %q, want %q", got, want)
}
v.Clear("bar")
// Attempt to Get while there is no value, should block indefinitely.
done := make(chan struct{})
go func() {
defer close(done)
v.Get()
}()
select {
case <-time.After(100 * time.Millisecond): // expected.
case <-done:
t.Errorf("Get() returned, expected it to block")
}
if got, err := v.GetWithTimeout(100 * time.Millisecond); err != ErrTimeout {
t.Errorf("GetWithTimeout() got %q (err: %v), want timeout", got, err)
}
v.Clear("bar") // has no effect, does not block.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment