go 45 lines · 1 tab

Context-aware cache refresh with atomic.Pointer (Go 1.19+)

Leah Thompson Jan 2026
1 tab
package cache

import (
  "context"
  "sync/atomic"
  "time"
)

type Snapshot map[string]string

type Store struct {
  p    atomic.Pointer[Snapshot]
  load func(context.Context) (Snapshot, error)
}

func New(load func(context.Context) (Snapshot, error)) *Store {
  s := &Store{load: load}
  empty := Snapshot{}
  s.p.Store(&empty)
  return s
}

func (s *Store) Get(k string) (string, bool) {
  snap := s.p.Load()
  v, ok := (*snap)[k]
  return v, ok
}

func (s *Store) Run(ctx context.Context, every time.Duration) {
  t := time.NewTicker(every)
  defer t.Stop()
  for {
    select {
    case <-ctx.Done():
      return
    case <-t.C:
      c, cancel := context.WithTimeout(ctx, 2*time.Second)
      snap, err := s.load(c)
      cancel()
      if err == nil {
        s.p.Store(&snap)
      }
    }
  }
}
1 file · go Explain with highlit

I often need a fast read path for small datasets (like a list of active plans or an allowlist) that updates periodically. Instead of locking on every read, I store a pointer to an immutable snapshot in atomic.Pointer. Reads are lock-free and safe; refresh happens in the background and swaps the pointer atomically. The key is immutability: never modify the map in place after publishing it, or you’ll race. I also make refresh honor context.Context and timeouts, and I keep the last good value if refresh fails. In production, this pattern keeps latency stable under load because readers never contend on mutexes. It’s a good fit when data changes slowly and correctness matters more than immediate consistency.


Related snips

Share this code

Here's the card — post it anywhere.

Context-aware cache refresh with atomic.Pointer (Go 1.19+) — share card
Link copied