pomerium/internal/testenv/values/value.go
Joe Kralicky 396c35b6b4
New tracing system (#5388)
* update tracing config definitions

* new tracing system

* performance improvements

* only configure tracing in envoy if it is enabled in pomerium

* [tracing] refactor to use custom extension for trace id editing (#5420)

refactor to use custom extension for trace id editing

* set default tracing sample rate to 1.0

* fix proxy service http middleware

* improve some existing auth related traces

* test fixes

* bump envoyproxy/go-control-plane

* code cleanup

* test fixes

* Fix missing spans for well-known endpoints

* import extension apis from pomerium/envoy-custom
2025-01-21 13:26:32 -05:00

166 lines
4.7 KiB
Go

package values
import (
"math/rand/v2"
"sync"
)
type value[T any] struct {
f func() T
ready bool
cond *sync.Cond
}
// A Value is a container for a single value of type T, whose initialization is
// performed the first time Value() is called. Subsequent calls will return the
// same value. The Value() function may block until the value is ready on the
// first call. Values are safe to use concurrently.
type Value[T any] interface {
Value() T
}
// MutableValue is the read-write counterpart to [Value], created by calling
// [Deferred] for some type T. Calling Resolve() or ResolveFunc() will set
// the value and unblock any waiting calls to Value().
type MutableValue[T any] interface {
Value[T]
Resolve(value T)
ResolveFunc(fOnce func() T)
}
// Deferred creates a new read-write [MutableValue] for some type T,
// representing a value whose initialization may be deferred to a later time.
// Once the value is available, call [MutableValue.Resolve] or
// [MutableValue.ResolveFunc] to unblock any waiting calls to Value().
func Deferred[T any]() MutableValue[T] {
return &value[T]{
cond: sync.NewCond(&sync.Mutex{}),
}
}
// Const creates a read-only [Value] which will become available immediately
// upon calling Value() for the first time; it will never block.
func Const[T any](t T) Value[T] {
return &value[T]{
f: func() T { return t },
ready: true,
cond: sync.NewCond(&sync.Mutex{}),
}
}
func (p *value[T]) Value() T {
p.cond.L.Lock()
defer p.cond.L.Unlock()
for !p.ready {
p.cond.Wait()
}
return p.f()
}
func (p *value[T]) ResolveFunc(fOnce func() T) {
p.cond.L.Lock()
p.f = sync.OnceValue(fOnce)
p.ready = true
p.cond.L.Unlock()
p.cond.Broadcast()
}
func (p *value[T]) Resolve(value T) {
p.ResolveFunc(func() T { return value })
}
// Bind creates a new [Value] whose ultimate value depends on the result
// of another [Value] that may not yet be available. When Value() is called on
// the result, it will cascade and trigger the full chain of initialization
// functions necessary to produce the final value.
//
// Care should be taken when using this function, as improper use can lead to
// deadlocks and cause values to never become available.
func Bind[T any, U any](dt Value[T], callback func(value T) U) Value[U] {
du := Deferred[U]()
du.ResolveFunc(func() U {
return callback(dt.Value())
})
return du
}
// Bind2 is like [Bind], but can accept two input values. The result will only
// become available once all input values become available.
//
// This function blocks to wait for each input value in sequence, but in a
// random order. Do not rely on the order of evaluation of the input values.
func Bind2[T any, U any, V any](dt Value[T], du Value[U], callback func(value1 T, value2 U) V) Value[V] {
dv := Deferred[V]()
dv.ResolveFunc(func() V {
if rand.IntN(2) == 0 { //nolint:gosec
return callback(dt.Value(), du.Value())
}
u := du.Value()
t := dt.Value()
return callback(t, u)
})
return dv
}
// List is a container for a slice of [Value] of type T, and is also a [Value]
// itself, for convenience. The Value() function will return a []T containing
// all resolved values for each element in the slice.
//
// A List's Value() function blocks to wait for each element in the slice in
// sequence, but in a random order. Do not rely on the order of evaluation of
// the slice elements.
type List[T any] []Value[T]
func (s List[T]) Value() []T {
values := make([]T, len(s))
for _, i := range rand.Perm(len(values)) {
values[i] = s[i].Value()
}
return values
}
// Chain is like [Bind], returning a Value[V] whose ultimate value depends on
// the result of a Value[U] which is obtained from the concrete T given in dt.
// It is intended to be used when T has a method returning a Value[U], and
// you want to access U, but don't need T.
//
// Example usage:
//
// type Foo interface { Number() Value[int] }
// var foo Foo
// values.Chain(foo, Foo.Number, func(number int) string { ... })
func Chain[T any, U any, V any](
dt Value[T],
callback1 func(value T) Value[U],
callback2 func(value U) V,
) Value[V] {
dv := Deferred[V]()
dv.ResolveFunc(func() V {
du := callback1(dt.Value())
return callback2(du.Value())
})
return dv
}
// Chain2 is like [Chain], but with two levels of indirection.
//
// Example usage:
//
// type Foo interface { Bar() Value[Bar] }
// type Bar interface { Number() Value[int] }
// var foo Foo
// values.Chain(foo, Foo.Bar, bar.Number, func(number int) string { ... })
func Chain2[T any, U any, V any, W any](
dt Value[T],
callback1 func(value T) Value[U],
callback2 func(value U) Value[V],
callback3 func(value V) W,
) Value[W] {
dw := Deferred[W]()
dw.ResolveFunc(func() W {
du := callback1(dt.Value())
dv := callback2(du.Value())
return callback3(dv.Value())
})
return dw
}