{"record":{"id":"29757c958adefa4d","repo":"temporalio/temporal","slug":"lru-cache-iterator-next-called-when-there-is-no-ne","errorCode":null,"errorMessage":"LRU cache iterator Next called when there is no next item","messagePattern":"LRU cache iterator Next called when there is no next item","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/cache/lru.go","lineNumber":77,"sourceCode":"\t\trefCount   int\n\t\tsize       int\n\t}\n)\n\n// Close closes the iterator\nfunc (it *iteratorImpl) Close() {\n\tit.lru.mut.Unlock()\n}\n\n// HasNext return true if there is more items to be returned\nfunc (it *iteratorImpl) HasNext() bool {\n\treturn it.nextItem != nil\n}\n\n// Next return the next item\nfunc (it *iteratorImpl) Next() Entry {\n\tif it.nextItem == nil {\n\t\tpanic(\"LRU cache iterator Next called when there is no next item\")\n\t}\n\n\tentry := it.nextItem.Value.(*entryImpl)\n\tit.nextItem = it.nextItem.Next()\n\t// make a copy of the entry so there will be no concurrent access to this entry\n\tentry = &entryImpl{\n\t\tkey:        entry.key,\n\t\tvalue:      entry.value,\n\t\tsize:       entry.size,\n\t\tcreateTime: entry.createTime,\n\t}\n\tit.prepareNext()\n\treturn entry\n}\n\nfunc (it *iteratorImpl) prepareNext() {\n\tfor it.nextItem != nil {\n\t\tentry := it.nextItem.Value.(*entryImpl)","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/cache/lru.go#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Guard every Next() call with it.HasNext()","Use the standard `for it.HasNext() { e := it.Next() ... }` pattern","Obtain a fresh iterator from the cache instead of reusing an exhausted one","Synchronize access so only one goroutine consumes a given iterator"],"exampleFix":"// before\nfor {\n    entry := it.Next() // panics when exhausted\n    process(entry)\n}\n// after\nfor it.HasNext() {\n    process(it.Next())\n}\n","handlingStrategy":"validation","validationCode":"if !it.HasNext() { return ErrNoMoreEntries }","typeGuard":"func safeNext(it cache.Iterator) (cache.Entry, bool) {\n    if !it.HasNext() {\n        return nil, false\n    }\n    return it.Next(), true\n}","tryCatchPattern":"func() (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            if strings.Contains(fmt.Sprint(r), \"no next item\") {\n                err = ErrIteratorExhausted\n            }\n        }\n    }()\n    return iterate(it)\n}","preventionTips":["Always iterate with `for it.HasNext()`","Never reuse an exhausted iterator; get a fresh one","Avoid manual counted loops over cache iterators","Only one goroutine should consume an iterator at a time"],"tags":["cache","lru","iterator","panic"],"backgroundTag":"iterator-exhausted","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}