{"record":{"id":"4b7bd4d713caf1c0","repo":"temporalio/temporal","slug":"simple-cache-iterator-next-called-when-there-is-no","errorCode":null,"errorMessage":"Simple cache iterator Next called when there is no next item","messagePattern":"Simple cache iterator Next called when there is no next item","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/cache/simple.go","lineNumber":46,"sourceCode":"\t\tkey   any\n\t\tvalue any\n\t}\n)\n\n// Close closes the iterator\nfunc (it *simpleItr) Close() {\n\tit.simple.RUnlock()\n}\n\n// HasNext return true if there is more items to be returned\nfunc (it *simpleItr) HasNext() bool {\n\treturn it.nextItem != nil\n}\n\n// Next returns the next item\nfunc (it *simpleItr) Next() Entry {\n\tif it.nextItem == nil {\n\t\tpanic(\"Simple cache iterator Next called when there is no next item\")\n\t}\n\n\t// nolint:revive\n\tentry := it.nextItem.Value.(*simpleEntry)\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 = &simpleEntry{\n\t\tkey:   entry.key,\n\t\tvalue: entry.value,\n\t}\n\treturn entry\n}\n\nfunc (e *simpleEntry) Key() any {\n\treturn e.key\n}\n\nfunc (e *simpleEntry) Value() any {","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/cache/simple.go#L28-L64","documentation":"The simple cache (common/cache/simple.go) iterator panics when Next() is called with no remaining item — the same contract as the LRU iterator. HasNext() must return true before Next() is legal. There is no graceful end-of-iteration return; the framework treats over-iteration as a caller bug.","triggerScenarios":"Calling Next() on a simpleItr after HasNext() returned false, or calling Next() before checking at all on an empty cache snapshot; calling Next() more times than the number of entries.","commonSituations":"Dumping/migrating all entries from a simple cache (e.g. namespace or shard caches) with a manual counter that overruns; iterating a cache that changed size between counting and iterating; copying LRU-style loop code into simple-cache code with an off-by-one.","solutions":["Check it.HasNext() before every Next() call","Iterate with `for it.HasNext() { e := it.Next() ... }`","Create a new iterator for each traversal instead of reusing exhausted ones"],"exampleFix":"// before\nfor i := 0; i < expectedCount; i++ {\n    e := it.Next() // panics if cache has fewer items\n}\n// after\nfor it.HasNext() {\n    e := it.Next()\n    process(e)\n}\n","handlingStrategy":"validation","validationCode":"for it.HasNext() {\n    e := it.Next()\n    process(e)\n}","typeGuard":"func nextOrNone(it *simpleItr) (cache.Entry, bool) {\n    if !it.HasNext() {\n        return nil, false\n    }\n    return it.Next(), true\n}","tryCatchPattern":null,"preventionTips":["Guard Next() with HasNext() unconditionally","Don't assume entry counts; iterate until HasNext is false","Create a fresh iterator per traversal"],"tags":["cache","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"}