Help us shape the future of Go! Take the 2026 Go Developer Survey

memorycache

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 4 Imported by: 0

README

Arkoren Cache Memory Driver

arkoren.dev/core/service/cache/memorycache is an in-process implementation of cache.Driver using only the standard library.

It is suitable for tests, local development, and single-process applications.

Using the driver

From your application's Go module, run go get arkoren.dev/core. Import the facade and driver, then use your operation's context.Context named ctx:

import (
	"time"

	"arkoren.dev/core/service/cache"
	"arkoren.dev/core/service/cache/memorycache"
)

store := cache.New(memorycache.New())
report := cache.NewKey[string]("reports:v1:daily", func(entry *cache.KeyBuilder) {
	entry.TTL(time.Minute)
})
err := store.Put(ctx, report, "ready")

Handle err before continuing. store.Get(ctx, report) then returns "ready" while the entry exists. State is not shared with another driver or process and disappears when this driver is discarded.

Optional cache.NewNamespace[T] definitions join names with ":" by default; standalone key names remain exact. Preserve deployed dot joins with NamespaceBuilder.Separator(".") before upgrading. Namespace composition is facade behavior, not storage isolation or a driver naming transformation.

Expiry is checked on access, so correctness never depends on sweeping. Writes also remove expired entries inline at most once per Cleanup (default one minute), so abandoned keys, such as rate-limit windows, do not grow memory without Start. Start additionally launches a janitor on the same interval; Stop ends it. A negative Builder.Cleanup disables all sweeping.

Every context-aware operation first returns ctx.Err() when canceled, before reading or changing cache state. Put and successful Add copy their byte input, and Get returns a byte copy; subsequent caller mutation cannot alter the cached value. A zero TTL has no expiration and a negative TTL is rejected with cache.ErrInvalidTTL. Increment and Decrement start missing counters from zero when the result fits int64. Pull, TryLock, Unlock, and ExtendLock are atomic under the same mutex.

Increment, Decrement, and IncrementWithTTL reject mathematical results outside int64 without changing the target value or TTL. Missing targets remain absent, and IncrementWithTTL does not add an expiry on overflow. These checks and writes hold the same mutex. Memory overflow errors wrap strconv.ErrRange; other drivers keep their native error identities. Decrement checks subtraction directly: subtracting math.MinInt64 from itself returns zero, but subtracting it from zero fails without mutation.

See the cache guide and memory driver reference.

Documentation

Overview

Package memorycache provides an in-memory cache.Driver for tests, local development, and single-process applications.

Every context-aware operation returns ctx.Err() before reading or mutating state. Put and successful Add copy supplied byte slices, and Get returns a copy, so caller mutation cannot change cached state. A zero TTL does not expire. CompareAndPut compares an existing guard and writes its destination under one mutex, including when the keys differ. Its successful write copies the supplied bytes; a mismatch preserves the entries and their lifetimes. Counter lookup, checked arithmetic, and write also share one mutex. Results outside int64 wrap strconv.ErrRange without changing the target value or TTL or creating a missing entry. Decrement checks subtraction directly, so a MinInt64 delta works whenever the mathematical result is representable.

New takes Builder policies for Cleanup. They run directly once in order on a fresh draft; nil entries panic and expanded nil/empty slices supply no edits. Zero cleanup resolves to one minute; negative disables sweeping. Publication detaches settings and retained drafts cannot change the driver's lifecycle. Construction starts no janitor; Start owns any background work.

Example:

driver := memorycache.New(func(store *memorycache.Builder) {
	store.Cleanup(-time.Second)
})
store := cache.New(driver)
_ = store.Driver()

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder configures New before publication. Callbacks run directly once, synchronously in order on one fresh draft. Nil callback entries panic; expanded nil or empty callback slices supply no edits. Retaining the draft cannot change published settings. Methods return the same draft.

Example:

memorycache.New(func(store *memorycache.Builder) {
	store.Cleanup(-time.Second)
})

func (*Builder) Cleanup

func (store *Builder) Cleanup(cleanup time.Duration) *Builder

Cleanup sets the minimum interval between inline expiry sweeps and, when started, janitor sweeps. Zero resolves to one minute; negative disables both kinds of sweeping. Expired entries are still removed when accessed.

This setting replaces the previous value on the construction draft. Services and loggers remain shared; publication detaches owned settings.

Example:

memorycache.New(func(store *memorycache.Builder) {
	store.Cleanup(-time.Second)
})

type BuilderFunc

type BuilderFunc = func(*Builder)

BuilderFunc configures a Builder. Example:

var policy memorycache.BuilderFunc = func(store *memorycache.Builder) {
	store.Cleanup(time.Minute)
}
_ = memorycache.New(policy)

type Driver

type Driver struct {
	// contains filtered or unexported fields
}

Driver implements cache.Driver in process memory.

Driver is suitable for single-process applications, local development, and tests where persistence across restarts is not required. It is safe for concurrent use. Expiry is checked on access, so correctness never depends on sweeping. Writes sweep expired entries inline at most once per Builder.Cleanup, so memory stays bounded without calling Driver.Start.

Example:

driver := memorycache.New()
if err := driver.Put(ctx, "version", []byte("v1"), time.Minute); err != nil {
	return err
}

func New

func New(configure ...BuilderFunc) *Driver

New creates a Driver with the completed construction policies. It starts no goroutines; unless Cleanup is negative, Driver.Start launches a janitor that Driver.Stop ends, and writes sweep expired entries inline either way. Expired entries are never returned, whether or not a sweep ran.

Construction callbacks run directly once, synchronously in order on one fresh draft. Nil entries panic; expanded nil or empty slices omit edits. Publication detaches owned settings, not live dependencies or captures.

Example:

memorycache.New(func(store *memorycache.Builder) {
	store.Cleanup(-time.Second)
})

func (*Driver) Add

func (driver *Driver) Add(ctx context.Context, key string, value []byte, ttl time.Duration) (bool, error)

Add stores a copy of value only when key is absent or expired.

Example:

added, err := driver.Add(ctx, "receipt", []byte("claimed"), time.Hour)
if err != nil {
	return err
}
_ = added

func (*Driver) CompareAndPut

func (driver *Driver) CompareAndPut(ctx context.Context, match cache.Match, entry cache.Entry) (bool, error)

CompareAndPut compares the existing guard and writes entry under one mutex. Missing, expired, or unequal guards return false without changing logical values or lifetimes. A matching guard permits a copied replacement with the requested TTL; a different guard key retains its content and expiry. Cancellation is checked both before waiting and after acquiring the mutex.

Example:

driver := memorycache.New()
if err := driver.Put(ctx, "document", []byte("draft"), time.Minute); err != nil {
	return err
}
applied, err := driver.CompareAndPut(ctx,
	cache.Match{Key: "document", Value: []byte("draft")},
	cache.Entry{Key: "document", Value: []byte("revised"), TTL: time.Minute})
if err != nil {
	return err
}
_ = applied

func (*Driver) Decrement

func (driver *Driver) Decrement(ctx context.Context, key string, delta int64) (int64, error)

Decrement decreases the integer stored at key by delta and returns the new value. A missing key is created from zero. The remaining TTL is preserved. Subtraction is checked directly, including delta == math.MinInt64. A result outside int64 returns an error wrapping strconv.ErrRange without changing the target value or expiry; a missing target remains absent.

Example:

count, err := driver.Decrement(ctx, "available", 1)
if err != nil {
	return err
}
_ = count

func (*Driver) Delete

func (driver *Driver) Delete(ctx context.Context, key string) error

Delete removes key. Deleting a missing key is a no-op.

Example:

if err := driver.Delete(ctx, "version"); err != nil {
	return err
}

func (*Driver) ExtendLock

func (driver *Driver) ExtendLock(ctx context.Context, name, token string, ttl time.Duration) (bool, error)

ExtendLock resets the expiry of name to ttl from now only while it holds token and reports whether it did. It returns an error wrapping cache.ErrInvalidTTL when ttl is not positive.

Example:

extended, err := driver.ExtendLock(ctx, "lock:report", token, time.Minute)
if err != nil {
	return err
}
_ = extended

func (*Driver) Get

func (driver *Driver) Get(ctx context.Context, key string) ([]byte, error)

Get returns a copy of the bytes stored for key. It returns cache.ErrNotFound when the key does not exist or has expired.

Example:

raw, err := driver.Get(ctx, "version")
if err != nil {
	return err
}
_ = raw // Independent bytes; mutation does not alter the stored value.

func (*Driver) Has

func (driver *Driver) Has(ctx context.Context, key string) (bool, error)

Has reports whether key exists and has not expired.

Example:

found, err := driver.Has(ctx, "version")
if err != nil {
	return err
}
_ = found // Observation only; use an atomic operation to guard a write.

func (*Driver) Increment

func (driver *Driver) Increment(ctx context.Context, key string, delta int64) (int64, error)

Increment increases the integer stored at key by delta and returns the new value. A missing key is created from zero. The remaining TTL is preserved. A mathematical result outside int64 returns an error wrapping strconv.ErrRange without changing the target value or TTL. An absent target remains absent.

Example:

count, err := driver.Increment(ctx, "views", 1)
if err != nil {
	return err
}
_ = count

func (*Driver) IncrementWithTTL

func (driver *Driver) IncrementWithTTL(ctx context.Context, key string, delta int64, ttl time.Duration) (int64, time.Duration, error)

IncrementWithTTL increases the integer stored at key by delta, ensures the key expires, and returns the new value with the remaining lifetime. A key that is missing or has no expiry gets ttl; an existing expiry is kept. It returns an error when ttl is not positive. A mathematical result outside int64 returns an error wrapping strconv.ErrRange before changing the target value or expiry. Overflow never adds a TTL to a persistent target or creates an absent one.

Example:

count, remaining, err := driver.IncrementWithTTL(ctx, "requests", 1, time.Minute)
if err != nil {
	return err
}
_, _ = count, remaining

func (*Driver) Pull

func (driver *Driver) Pull(ctx context.Context, key string) ([]byte, error)

Pull returns the bytes stored for key and removes the entry under the same lock, so concurrent callers cannot both receive the value. It returns cache.ErrNotFound when the key does not exist or has expired.

Example:

raw, err := driver.Pull(ctx, "one-time-token")
if err != nil {
	return err
}
_ = raw // The entry was removed atomically with this read.

func (*Driver) Put

func (driver *Driver) Put(ctx context.Context, key string, value []byte, ttl time.Duration) error

Put stores a copy of value for key. A zero ttl stores the entry without expiration.

Example:

if err := driver.Put(ctx, "version", []byte("v1"), time.Minute); err != nil {
	return err
}

func (*Driver) Start

func (driver *Driver) Start(ctx context.Context) error

Start launches the janitor unless Cleanup is negative. Starting an already started driver does nothing.

Example:

driver := memorycache.New()
if err := driver.Start(ctx); err != nil {
	return err
}
return driver.Stop(ctx)

func (*Driver) Stop

func (driver *Driver) Stop(ctx context.Context) error

Stop ends the janitor and waits for it to exit or ctx to end. The driver stays usable afterwards; expired entries are then removed by access and by inline sweeps on writes.

Example:

cleanup, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := driver.Stop(cleanup); err != nil {
	return err
}

func (*Driver) TTL

func (driver *Driver) TTL(ctx context.Context, key string) (time.Duration, error)

TTL returns the remaining lifetime for key, or zero when it never expires. It returns cache.ErrNotFound when the key does not exist or has expired.

Example:

remaining, err := driver.TTL(ctx, "version")
if err != nil {
	return err
}
_ = remaining

func (*Driver) TryLock

func (driver *Driver) TryLock(ctx context.Context, name, token string, ttl time.Duration) (bool, error)

TryLock stores token under name for ttl when name is absent or expired, and reports whether it did. The entry is an ordinary one, so an expired lock is replaced like any expired key. It returns an error wrapping cache.ErrInvalidTTL when ttl is not positive.

Example:

token := rand.Text() // crypto/rand: unique to this attempt.
held, err := driver.TryLock(ctx, "lock:report", token, time.Minute)
if err != nil {
	return err
}
if held {
	_, err = driver.Unlock(ctx, "lock:report", token)
}
return err

func (*Driver) Unlock

func (driver *Driver) Unlock(ctx context.Context, name, token string) (bool, error)

Unlock removes name only while it holds token and reports whether it did, comparing and deleting under one lock acquisition.

Example:

released, err := driver.Unlock(ctx, "lock:report", token)
if err != nil {
	return err
}
_ = released

Directories

Path Synopsis
internal
storage
Package storage owns atomic cache values, counters, expiry and sweeping.
Package storage owns atomic cache values, counters, expiry and sweeping.

Jump to

Keyboard shortcuts

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