temporalio/temporal · error
Cannot use Put API in Pin mode. Use Delete and PutIfNotExist
Error message
Cannot use Put API in Pin mode. Use Delete and PutIfNotExist if necessary
What it means
common/cache/lru.go's Put panics when the cache is created in Pin mode. A pinned cache has a fixed working set (entries are effectively locked in), so unconditional Put would violate pinning semantics; the API instead directs users to Delete followed by PutIfNotExist. The panic enforces the restricted API surface for pinned caches.
Source
Thrown at common/cache/lru.go:220
entry := element.Value.(*entryImpl)
if c.isEntryExpired(entry, c.timeSource.Now().UTC()) {
// Entry has expired
c.deleteInternal(element)
return nil
}
metrics.CacheEntryAgeOnGet.With(c.metricsHandler).Record(c.timeSource.Now().UTC().Sub(entry.createTime))
c.updateEntryRefCount(entry)
c.byAccess.MoveToFront(element)
return entry.value
}
// Put puts a new value associated with a given key, returning the existing value (if present)
func (c *lru) Put(key any, value any) any {
if c.pin {
panic("Cannot use Put API in Pin mode. Use Delete and PutIfNotExist if necessary")
}
val, _ := c.putInternal(key, value, true)
return val
}
// PutIfNotExist puts a value associated with a given key if it does not exist
func (c *lru) PutIfNotExist(key any, value any) (any, error) {
existing, err := c.putInternal(key, value, false)
if err != nil {
return nil, err
}
if existing == nil {
// This is a new value
return value, err
}
return existing, errView on GitHub (pinned to bde624efd1)
Solutions
- On a pinned cache, replace Put with PutIfNotExist (and Delete first if replacement is truly required)
- Remove Pin mode if the workload genuinely needs unconditional overwrite semantics
- Route all writes through a wrapper that branches on the cache's pin mode
Example fix
// before
if old := cache.Put(key, val); old != nil { ... }
// after (pinned cache)
cache.Delete(key)
if cache.PutIfNotExist(key, val) { ... }
Defensive patterns
Strategy: validation
Validate before calling
if cacheIsPinned {
cache.Delete(key)
cache.PutIfNotExist(key, val)
} else {
cache.Put(key, val)
} Type guard
func canPut(c *lru) bool { return !c.pin } Prevention
- Branch write logic on the cache's pin mode
- Prefer PutIfNotExist for read-through caching patterns
- Document pinned caches at creation sites so callers know Put is unavailable
When it happens
Trigger: Creating a cache with the pin option enabled (Cache(Options{Pin: true}) or equivalent) and then calling c.Put(key, value) directly.
Common situations: Cache size set equal to expected working set with Pin to prevent eviction, then generic cache-writing code that uses Put; refactored code sharing a Put helper between pinned and non-pinned caches; migration from a non-pinned to a pinned cache without updating call sites.
Related errors
- LRU cache iterator Next called when there is no next item
- chasm visibilityTaskHandler should not be called directly
- internal error: call to RetryLockedSource.Seed
- Simple cache iterator Next called when there is no next item
- error loading nexus endpoints cache: %w
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/8e4e2d30766d1a73.
Report an issue: GitHub.