Documentation ¶
Overview ¶
Package oncecache contains a strongly-typed, concurrency-safe, context-aware, dependency-free, in-memory, on-demand object Cache, focused on write-once, read-often ergonomics.
The package also provides an event mechanism useful for logging, metrics, or propagating cache entries between overlapping composite caches.
Index ¶
- Variables
- func NewContext[K comparable, V any](ctx context.Context, c *Cache[K, V]) context.Context
- type Cache
- func (c *Cache[K, V]) Clear(ctx context.Context)
- func (c *Cache[K, V]) Close() error
- func (c *Cache[K, V]) Delete(ctx context.Context, key K)
- func (c *Cache[K, V]) Get(ctx context.Context, key K) (V, error)
- func (c *Cache[K, V]) Has(key K) bool
- func (c *Cache[K, V]) Keys() []K
- func (c *Cache[K, V]) Len() int
- func (c *Cache[K, V]) LogValue() slog.Value
- func (c *Cache[K, V]) MaybeSet(ctx context.Context, key K, val V, err error) (ok bool)
- func (c *Cache[K, V]) Name() string
- func (c *Cache[K, V]) String() string
- type Entry
- type Event
- type FetchFunc
- type Name
- type Op
- type Opt
- func Log(log *slog.Logger, lvl slog.Leveler, ops ...Op) Opt
- func OnEvent[K comparable, V any](ch chan<- Event[K, V], block bool, ops ...Op) Opt
- func OnEvict[K comparable, V any](fn func(ctx context.Context, key K, val V, err error)) Opt
- func OnFill[K comparable, V any](fn func(ctx context.Context, key K, val V, err error)) Opt
- func OnHit[K comparable, V any](fn func(ctx context.Context, key K, val V, err error)) Opt
- func OnMiss[K comparable, V any](fn func(ctx context.Context, key K)) Opt
Constants ¶
This section is empty.
Variables ¶
var LogConfig = struct { Msg string AttrEvent string AttrCache string AttrOp string AttrKey string AttrVal string AttrErr string }{ Msg: "Cache event", AttrEvent: "ev", AttrCache: "cache", AttrOp: "op", AttrKey: "k", AttrVal: "v", AttrErr: "err", }
LogConfig is used by oncecache.Log to configure log output. See also: oncecache.Event.LogValue, oncecache.Entry.LogValue.
Functions ¶
func NewContext ¶
NewContext returns ctx decorated with Cache c. If ctx is nil, a new context is created.
Types ¶
type Cache ¶
type Cache[K comparable, V any] struct { // contains filtered or unexported fields }
Cache is a concurrency-safe, in-memory, on-demand cache that ensures that a given cache entry is populated only once, either implicitly via Cache.Get and the fetch func, or externally via [Cache.Set].
However, a cache entry can be explicitly cleared via Cache.Delete or Cache.Clear, allowing the entry to be populated afresh.
A cache entry consists not only of the key and value, but also any error associated with filling the entry value via the fetch func or via [Cache.Set]. Thus, a cache entry is a triple: (key, value, error). An entry with a non-nil error is still a valid cache entry. A call to Cache.Get for an existing errorful cache entry does not invoke the fetch func again. Cache entry population occurs only once (hence "oncecache"), unless the entry is explicitly evicted via Cache.Delete or Cache.Clear.
The zero value is not usable; instead invoke New.
func FromContext ¶
func FromContext[K comparable, V any](ctx context.Context) *Cache[K, V]
FromContext returns the Cache value stored in ctx, if any, or nil. All cache callbacks receive a context that has been decorated with the Cache instance.
func New ¶
func New[K comparable, V any](fetch FetchFunc[K, V], opts ...Opt) *Cache[K, V]
New returns a new Cache instance. The fetch func is invoked, on-demand, by Cache.Get to obtain an entry value for a given key, OR the entry may be externally set via [Cache.Set]. Either which way, the entry is populated only once. That is, unless the entry is explicitly cleared via Cache.Delete or Cache.Clear, at which point the entry may be populated afresh.
Arg opts is a set of options that can be used to configure the cache. For example, see Name to set the cache name, or the OnFill or OnEvict options for event callbacks. Any nil Opt in opts is ignored.
func (*Cache[K, V]) Clear ¶
Clear clears the cache entries, invoking any OnEvict callbacks on each cache entry. The entry callback order is not specified. The cache is locked until Clear (including any callbacks) returns.
func (*Cache[K, V]) Close ¶
Close closes the cache, releasing any resources. Close is idempotent and always returns nil. Callbacks are not invoked. The cache is not usable after Close is invoked; calls to other Cache methods may panic.
func (*Cache[K, V]) Delete ¶
Delete deletes the entry for the given key, invoking any OnEvict callbacks. The cache is locked until Delete (including any callbacks) returns.
func (*Cache[K, V]) Get ¶
Get gets the value (and fill error) for the given key. If there's no entry for the key, the fetch func is invoked, setting the entry value and error. If the entry is already populated, the value and error are returned without invoking the fetch func. Any OnHit, OnMiss, and OnFill callbacks are invoked, and OpHit, OpMiss and OpFill events emitted, as appropriate.
func (*Cache[K, V]) Keys ¶
func (c *Cache[K, V]) Keys() []K
Keys returns the cache keys. The keys will be in an indeterminate order.
func (*Cache[K, V]) LogValue ¶
LogValue implements slog.LogValuer.
func (*Cache[K, V]) MaybeSet ¶
MaybeSet sets the value and fill error for the given key if it is not already filled, returning true if the value was set. This would allow an external process to prime the cache.
Note that the value might instead be filled implicitly via Cache.Get, when it invokes the fetch func. If there's already a cache entry for key, MaybeSet is no-op: the value is not updated. If this MaybeSet call does update the cache entry, any OnFill callbacks - as provided to New - are invoked, and ok returns true.
func (*Cache[K, V]) Name ¶
Name returns the cache's name, useful for logging. Specify the cache name by passing oncecache.Name to New; otherwise a random name is used.
type Entry ¶
type Entry[K comparable, V any] struct { Cache *Cache[K, V] Key K Val V Err error }
Entry is the external representation of a cache entry. It is not part of the cache's internal state; it can be modified by the user if desired.
func (Entry[K, V]) LogValue ¶
LogValue implements slog.LogValuer, logging Val if it implements slog.LogValuer or is a primitive type such as int or bool (but not string), logging Err if non-nil, and always logging Key and Cache.Name.
func (Entry[K, V]) String ¶
String returns a string representation of the entry. The entry's Val field is not incorporated. For logging, note Entry.LogValue.
type Event ¶
type Event[K comparable, V any] struct { Entry[K, V] Op Op }
Event is a cache event.
func (Event[K, V]) LogValue ¶
LogValue implements slog.LogValuer, logging according to Entry.LogValue, but also logging [Event.Op].
func (Event[K, V]) String ¶
String returns a string representation of the event. The event's Val field is not incorporated. For logging, note Event.LogValue.
type FetchFunc ¶
type FetchFunc[K comparable, V any] func(ctx context.Context, key K) (val V, err error)
FetchFunc is called by Cache.Get to fill an unpopulated cache entry. If needed, the source Cache can be retrieved from ctx via FromContext.
type Name ¶
type Name string
Name is an Opt for New that sets the cache's name. The name is accessible via Cache.Name.
c := oncecache.New[int, string](fetch, oncecache.Name("foobar"))
The name is used by Cache.String and Cache.LogValue. If Name is not specified, a random name such as "cache-38a2b7d4" is generated.
type Op ¶
type Op uint8
Op is an enumeration of cache operations, as see in [Event.Op].
const ( // OpHit indicates a cache hit: a cache entry already exists for the key. Note // that the cache entry may contain a non-nil error, and the entry value may // be the zero value. An errorful cache entry is a valid hit. OpHit Op = 1 // OpMiss indicates a cache miss. It is always immediately followed by an // [OpFill]. OpMiss Op = 2 // OpFill indicates that a cache entry has been populated. Typically it is // immediately preceded by [OpMiss], but will occur standalone when // [Cache.Set] is invoked. Note that if the entry fill results in an error, // the entry is still considered valid, and [OpFill] is still emitted. OpFill Op = 3 // OpEvict indicates a cache entry has been removed. OpEvict Op = 4 )
type Opt ¶
type Opt interface {
// contains filtered or unexported methods
}
Opt is an option for New.
func Log ¶
Log is an Opt for oncecache.New that logs each Event to log, where [Event.Op] is in ops. If ops is empty, all events are logged. If log is nil, Log is a no-op. If lvl is nil, slog.LevelInfo is used. Example usage:
c := oncecache.New[int, int]( calcFibonacci, oncecache.Name("fibs"), oncecache.Log(log, slog.LevelInfo, oncecache.OpFill, oncecache.OpEvict), oncecache.Log(log, slog.LevelDebug, oncecache.OpHit, oncecache.OpMiss), )
Log is intended to handle basic logging needs. Some configuration is possible via oncecache.LogConfig. If you require more control, you can roll your own logging mechanism using an OnEvent channel or the On* callbacks. If doing so, note that Event, Entry and Cache each implement slog.LogValuer.
func OnEvent ¶
OnEvent is an Opt argument to New that configures the cache to emit events on the given chan. If ops is empty, all events are emitted; otherwise, only events for the given ops are emitted.
If arg block is true, the Cache function that triggered the event will block on sending to a full ch. If false, the new event is dropped if ch is full.
You can use an unbuffered channel and block=true to stop the event consumer from falling too far behind the cache state. Alternatively the synchronous OnHit, OnMiss, OnFill, and OnEvict callbacks can be used, at cost of increased lock contention and lower throughput.
For basic logging, consider oncecache.Log.
func OnEvict ¶
OnEvict returns a callback Opt for New that is invoked when a cache entry is evicted via Cache.Delete or Cache.Clear.
Note that OnEvict callbacks are synchronous; the triggering call to Cache.Delete or Cache.Clear blocks until every OnEvict returns. Consider using OnEvent for long-running callbacks.
func OnFill ¶
OnFill returns a callback Opt for New that is invoked when a cache entry is populated, whether on-demand via Cache.Get and FetchFunc, or externally via Cache.MaybeSet.
Note that OnFill callbacks are synchronous; the triggering call to Cache.MaybeSet or Cache.Get blocks until every OnFill returns. Consider using OnEvent for long-running callbacks.
While OnFill can be used for logging, metrics, etc., most common tasks are better accomplished via OnEvent.
func OnHit ¶
OnHit returns a callback Opt for New that is invoked when Cache.Get results in a cache hit.
Note that OnHit callbacks are synchronous; the triggering call to Cache.Get blocks until every OnHit returns. Consider using the asynchronous OnEvent for long-running callbacks.
func OnMiss ¶
func OnMiss[K comparable, V any](fn func(ctx context.Context, key K)) Opt
OnMiss returns a callback Opt for New that is invoked when Cache.Get results in a cache miss.
Note that OnMiss callbacks are synchronous; the triggering call to Cache.Get blocks until every OnMiss returns. Consider using the asynchronous OnEvent for long-running callbacks.
FIXME: Starting to think OnMiss should just use the standard callback signature.