helper/shadow: OrderedValue
This commit is contained in:
parent
3522b07b75
commit
8426cea6b0
|
@ -0,0 +1,5 @@
|
|||
package shadow
|
||||
|
||||
// KeyedValue is a struct that coordinates a value by key.
|
||||
type KeyedValue struct {
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package shadow
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// OrderedValue is a struct that keeps track of a value in the order
|
||||
// it is set. Each time Value() is called, it will return the most recent
|
||||
// calls value then discard it.
|
||||
//
|
||||
// This is unlike Value that returns the same value once it is set.
|
||||
type OrderedValue struct {
|
||||
lock sync.Mutex
|
||||
values *list.List
|
||||
waiters *list.List
|
||||
}
|
||||
|
||||
// Value returns the last value that was set, or blocks until one
|
||||
// is received.
|
||||
func (w *OrderedValue) Value() interface{} {
|
||||
w.lock.Lock()
|
||||
|
||||
// If we have a pending value already, use it
|
||||
if w.values != nil && w.values.Len() > 0 {
|
||||
front := w.values.Front()
|
||||
w.values.Remove(front)
|
||||
w.lock.Unlock()
|
||||
return front.Value
|
||||
}
|
||||
|
||||
// No pending value, create a waiter
|
||||
if w.waiters == nil {
|
||||
w.waiters = list.New()
|
||||
}
|
||||
|
||||
var val Value
|
||||
w.waiters.PushBack(&val)
|
||||
w.lock.Unlock()
|
||||
|
||||
// Return the value once we have it
|
||||
return val.Value()
|
||||
}
|
||||
|
||||
// SetValue sets the latest value.
|
||||
func (w *OrderedValue) SetValue(v interface{}) {
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
// If we have a waiter, notify it
|
||||
if w.waiters != nil && w.waiters.Len() > 0 {
|
||||
front := w.waiters.Front()
|
||||
w.waiters.Remove(front)
|
||||
|
||||
val := front.Value.(*Value)
|
||||
val.SetValue(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Add it to the list of values
|
||||
if w.values == nil {
|
||||
w.values = list.New()
|
||||
}
|
||||
|
||||
w.values.PushBack(v)
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package shadow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestOrderedValue(t *testing.T) {
|
||||
var v OrderedValue
|
||||
|
||||
// Start trying to get the value
|
||||
valueCh := make(chan interface{})
|
||||
go func() {
|
||||
valueCh <- v.Value()
|
||||
}()
|
||||
|
||||
// We should not get the value
|
||||
select {
|
||||
case <-valueCh:
|
||||
t.Fatal("shouldn't receive value")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
// Set the value
|
||||
v.SetValue(42)
|
||||
val := <-valueCh
|
||||
|
||||
// Verify
|
||||
if val != 42 {
|
||||
t.Fatalf("bad: %#v", val)
|
||||
}
|
||||
|
||||
// We should not get the value again
|
||||
go func() {
|
||||
valueCh <- v.Value()
|
||||
}()
|
||||
select {
|
||||
case <-valueCh:
|
||||
t.Fatal("shouldn't receive value")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
// We should get the next value
|
||||
v.SetValue(21)
|
||||
val = <-valueCh
|
||||
if val != 21 {
|
||||
t.Fatalf("bad: %#v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderedValue_setFirst(t *testing.T) {
|
||||
var v OrderedValue
|
||||
|
||||
// Set the value
|
||||
v.SetValue(42)
|
||||
val := v.Value()
|
||||
|
||||
// Verify
|
||||
if val != 42 {
|
||||
t.Fatalf("bad: %#v", val)
|
||||
}
|
||||
|
||||
// We should not get the value again
|
||||
valueCh := make(chan interface{})
|
||||
go func() {
|
||||
valueCh <- v.Value()
|
||||
}()
|
||||
select {
|
||||
case <-valueCh:
|
||||
t.Fatal("shouldn't receive value")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
// Set a value so the goroutine doesn't hang around
|
||||
v.SetValue(1)
|
||||
}
|
Loading…
Reference in New Issue