temporalio/temporal · error

LRU cache iterator Next called when there is no next item

Error message

LRU cache iterator Next called when there is no next item

What it means

The LRU cache iterator in common/cache/lru.go panics when Next() is called after the iterator is exhausted (or before any call when there is no item). Callers must use HasNext() to check availability before Next(); iterating past the end is treated as a contract violation, not a returned error.

Source

Thrown at common/cache/lru.go:77

		refCount   int
		size       int
	}
)

// Close closes the iterator
func (it *iteratorImpl) Close() {
	it.lru.mut.Unlock()
}

// HasNext return true if there is more items to be returned
func (it *iteratorImpl) HasNext() bool {
	return it.nextItem != nil
}

// Next return the next item
func (it *iteratorImpl) Next() Entry {
	if it.nextItem == nil {
		panic("LRU cache iterator Next called when there is no next item")
	}

	entry := it.nextItem.Value.(*entryImpl)
	it.nextItem = it.nextItem.Next()
	// make a copy of the entry so there will be no concurrent access to this entry
	entry = &entryImpl{
		key:        entry.key,
		value:      entry.value,
		size:       entry.size,
		createTime: entry.createTime,
	}
	it.prepareNext()
	return entry
}

func (it *iteratorImpl) prepareNext() {
	for it.nextItem != nil {
		entry := it.nextItem.Value.(*entryImpl)

View on GitHub (pinned to bde624efd1)

Solutions

  1. Guard every Next() call with it.HasNext()
  2. Use the standard `for it.HasNext() { e := it.Next() ... }` pattern
  3. Obtain a fresh iterator from the cache instead of reusing an exhausted one
  4. Synchronize access so only one goroutine consumes a given iterator

Example fix

// before
for {
    entry := it.Next() // panics when exhausted
    process(entry)
}
// after
for it.HasNext() {
    process(it.Next())
}
Defensive patterns

Strategy: validation

Validate before calling

if !it.HasNext() { return ErrNoMoreEntries }

Type guard

func safeNext(it cache.Iterator) (cache.Entry, bool) {
    if !it.HasNext() {
        return nil, false
    }
    return it.Next(), true
}

Try / catch

func() (err error) {
    defer func() {
        if r := recover(); r != nil {
            if strings.Contains(fmt.Sprint(r), "no next item") {
                err = ErrIteratorExhausted
            }
        }
    }()
    return iterate(it)
}

Prevention

When it happens

Trigger: Calling it.Next() when it.HasNext() is false — typically a loop like `for { item := it.Next() ... }` without checking HasNext, or calling Next one extra time after a `for it.HasNext()` loop.

Common situations: Off-by-one in manual iteration over an LRU cache (e.g. draining a session/namespace cache); reusing an exhausted iterator for a second pass; concurrent iteration where another goroutine's consumption exhausts the iterator.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/29757c958adefa4d. Report an issue: GitHub.