expirationcache

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MPL-2.0 Imports: 7 Imported by: 5

README

ExpirationLRUCache

A generic, thread-safe LRU cache for Go with per-item expiration and flexible expiration/callback hooks.

Features

  • LRU eviction policy with configurable max size
  • Per-item expiration (TTL)
  • Optional periodic cleanup
  • Pre-expiration callback to refresh or remove items
  • Cache hit/miss/put hooks
  • Safe for concurrent use
  • Optional N-way sharding to remove single-lock read contention on multi-core systems

Installation

go get github.com/0xERR0R/expiration-cache

Usage

Basic Usage
import (
    "context"
    "time"
    "github.com/0xERR0R/expiration-cache"
)

func main() {
    cache := expirationcache.NewCache[string](context.Background(), expirationcache.Options{})
    v := "hello"
    cache.Put("key1", &v, 5*time.Second)
    val, ttl := cache.Get("key1")
    if val != nil {
        println(*val, ttl.String())
    }
    cache.Remove("key1") // delete a single entry (no-op if absent)
}
With Expiration
cache := expirationcache.NewCache[int](context.Background(), expirationcache.Options{CleanupInterval: time.Second})
v := 42
cache.Put("answer", &v, 2*time.Second)
time.Sleep(3 * time.Second)
val, _ := cache.Get("answer") // val will be nil (expired)
With Callbacks
cache := expirationcache.NewCache[string](context.Background(), expirationcache.Options{
    OnCacheHitFn: func(key string) { println("hit:", key) },
    OnCacheMissFn: func(key string) { println("miss:", key) },
    OnAfterPutFn: func(size int) { println("cache size:", size) },
})
v := "data"
cache.Put("k", &v, time.Second)
cache.Get("k") // prints: hit: k
cache.Get("notfound") // prints: miss: notfound
With Pre-Expiration Function
refreshFn := func(ctx context.Context, key string) (*string, time.Duration) {
    refreshed := "refreshed-value"
    return &refreshed, 5 * time.Second // refresh value and TTL
}
cache := expirationcache.NewCacheWithOnExpired[string](context.Background(), expirationcache.Options{}, refreshFn)
v := "old"
cache.Put("k", &v, time.Second)
// After 1s, the refreshFn will be called before removal, and the value will be refreshed.
With Sharding

Every Get takes the LRU's lock to update recency, so on multi-core systems all reads serialize on a single mutex. Set Shards to split the cache into N independent LRUs (rounded up to a power of two); keys are routed to a shard by hash, so reads on different shards never contend. Shards: 0 or 1 (the default) means a single shard — byte-for-byte identical to the unsharded cache.

cache := expirationcache.NewCache[string](context.Background(), expirationcache.Options{
    MaxSize: 100_000,
    Shards:  16, // around GOMAXPROCS is a good starting point
})

MaxSize is distributed across the shards so the total never exceeds it, but eviction is per-shard rather than globally LRU: a hot shard can evict entries while others still have room, so the effective capacity is approximate — a standard sharded-cache trade-off that loosens as the shard count grows.

Sharding only helps when multiple goroutines run in parallel on multiple CPUs. On a single CPU (GOMAXPROCS=1) there is nothing to parallelize and it slightly degrades eviction quality, so leave Shards at the default unless you actually have contention to relieve.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ExpirationLRUCache

type ExpirationLRUCache[T any] struct {
	// contains filtered or unexported fields
}

ExpirationLRUCache is an LRU cache with per-item expiration and optional callbacks. It is safe for concurrent use by multiple goroutines.

func NewCache

func NewCache[T any](ctx context.Context, options Options) *ExpirationLRUCache[T]

NewCache creates a new ExpirationLRUCache with the given options. The cache is safe for concurrent use by multiple goroutines.

ctx: Context for controlling the lifetime of the background cleanup goroutine. options: Configuration for cache behavior.

func NewCacheWithOnExpired

func NewCacheWithOnExpired[T any](ctx context.Context, options Options,
	onExpirationFn OnExpirationCallback[T],
) *ExpirationLRUCache[T]

NewCacheWithOnExpired creates a new ExpirationLRUCache with the given options and a custom expiration callback. The cache is safe for concurrent use by multiple goroutines.

ctx: Context for controlling the lifetime of the background cleanup goroutine. options: Configuration for cache behavior. onExpirationFn: Callback invoked before an item expires; can return a new value and TTL to keep the item alive.

func (*ExpirationLRUCache[T]) Clear

func (e *ExpirationLRUCache[T]) Clear()

Clear removes all items from the cache.

func (*ExpirationLRUCache[T]) Get

func (e *ExpirationLRUCache[T]) Get(key string) (val *T, ttl time.Duration)

Get retrieves a value from the cache by key. Can return already expired value. Returns the value pointer and remaining TTL if found, or (nil, 0) if not found.

key: The cache key.

func (*ExpirationLRUCache[T]) Put

func (e *ExpirationLRUCache[T]) Put(key string, val *T, ttl time.Duration)

Put adds a value to the cache with the specified key and TTL (time-to-live). If ttl <= 0, the entry is not added.

key: The cache key. val: Pointer to the value to store. ttl: Duration before the item expires.

func (*ExpirationLRUCache[T]) Remove added in v0.2.0

func (e *ExpirationLRUCache[T]) Remove(key string)

Remove deletes the entry with the given key from the cache. Removing a key that is not present is a no-op. The live counter is kept in sync by the LRU's eviction callback, so TotalCount reflects the removal.

key: The cache key to remove.

func (*ExpirationLRUCache[T]) TotalCount

func (e *ExpirationLRUCache[T]) TotalCount() int

TotalCount returns the current number of items in the cache.

type OnAfterPutCallback

type OnAfterPutCallback func(newSize int)

OnAfterPutCallback will be called after put, receives new element count as parameter

type OnCacheHitCallback

type OnCacheHitCallback func(key string)

OnCacheHitCallback will be called on cache get if entry was found

type OnCacheMissCallback

type OnCacheMissCallback func(key string)

OnCacheMissCallback will be called on cache get and entry was not found

type OnExpirationCallback

type OnExpirationCallback[T any] func(ctx context.Context, key string) (val *T, ttl time.Duration)

OnExpirationCallback will be called just before an element gets expired and will be removed from cache. This function can return new value and TTL to leave the element in the cache or nil to remove it

type Options

type Options struct {
	OnCacheHitFn    OnCacheHitCallback
	OnCacheMissFn   OnCacheMissCallback
	OnAfterPutFn    OnAfterPutCallback
	CleanupInterval time.Duration
	MaxSize         uint
	// Shards sets the number of independent LRU shards the cache is split into,
	// rounded up to a power of two (and clamped to a sane maximum). Keys are routed
	// to a shard by hash, so reads and writes on different shards never contend on
	// the same lock. 0 or 1 means a single shard (no sharding) and matches
	// pre-sharding behavior; this is the default.
	//
	// Sharding only improves throughput when multiple goroutines run in parallel on
	// multiple CPUs, because Get takes a write lock to update LRU recency. On a
	// single CPU it yields no benefit and slightly degrades eviction quality, so
	// leave it at the default unless GOMAXPROCS > 1; a value near GOMAXPROCS is a
	// reasonable starting point.
	Shards uint
}

Options configures the behavior of ExpirationLRUCache.

OnCacheHitFn: Optional callback invoked when a cache hit occurs. OnCacheMissFn: Optional callback invoked when a cache miss occurs. OnAfterPutFn: Optional callback invoked after a new item is put in the cache. CleanupInterval: How often expired items are cleaned up (default 10s). MaxSize: Maximum total number of items across all shards (default 10,000). The capacity is divided across shards so the total never exceeds MaxSize; with more than one shard, eviction is per-shard, so a hot shard may evict entries while others still have room and the effective capacity is approximate.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL