{"record":{"id":"77a3ced06c4d1fd2","repo":"temporalio/temporal","slug":"reader-with-id-v-already-exists","errorCode":null,"errorMessage":"reader with ID %v already exists","messagePattern":"reader with ID (.+?) already exists","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/queues/reader_group.go","lineNumber":129,"sourceCode":"\t\treturn nil, false\n\t}\n\n\treader, ok := g.readerMap[readerID]\n\treturn reader, ok\n}\n\nfunc (g *ReaderGroup) NewReader(readerID int64, slices ...Slice) Reader {\n\tg.Lock()\n\tdefer g.Unlock()\n\n\treturn g.newReaderLocked(readerID, slices...)\n}\n\nfunc (g *ReaderGroup) newReaderLocked(readerID int64, slices ...Slice) Reader {\n\treader := g.initializer(readerID, slices)\n\n\tif _, ok := g.readerMap[readerID]; ok {\n\t\tpanic(fmt.Sprintf(\"reader with ID %v already exists\", readerID))\n\t}\n\n\tg.readerMap[readerID] = reader\n\n\tif g.isStarted() {\n\t\treader.Start()\n\t}\n\treturn reader\n}\n\nfunc (g *ReaderGroup) RemoveReader(readerID int64) {\n\tg.Lock()\n\tdefer g.Unlock()\n\n\treader, ok := g.readerMap[readerID]\n\tif !ok {\n\t\treturn\n\t}","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/queues/reader_group.go#L111-L147","documentation":"ReaderGroup.newReaderLocked panics when a reader with the same numeric ID already exists in the group's readerMap. Reader IDs are unique keys for in-memory readers; GetOrCreateReader guards with a map lookup, so hitting this panic means a race or a bug bypassed the check — e.g. NewReader was called directly with a taken ID, or two goroutines raced without the group lock.","triggerScenarios":"Calling ReaderGroup.NewReader(id, ...) with an ID that already exists; a race where GetOrCreateReader's locked check and creation interleave incorrectly; deterministic ID generation colliding after reader recycling.","commonSituations":"Custom code paths calling NewReader instead of GetOrCreateReader; ID reuse after a reader was expected to be closed but wasn't removed from the map; tests constructing multiple readers with constant IDs.","solutions":["Use GetOrCreateReader instead of NewReader so existing readers are returned rather than duplicated","Ensure all reader creation goes through the ReaderGroup under its lock; never hold a stale reader reference that wasn't closed","Check the reader lifecycle: confirm the reader with that ID was closed and removed from the group before reusing the ID"],"exampleFix":"// before\nreader := group.NewReader(readerID, slices...) // panics if ID exists\n\n// after\nreader := group.GetOrCreateReader(readerID, slices...)","handlingStrategy":"validation","validationCode":"// use the group API instead of creating directly\nif r := group.GetReader(readerID); r == nil {\n  r = group.GetOrCreateReader(readerID, slices...)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always create readers via GetOrCreateReader","Never reuse a reader ID until the previous reader is closed and removed","In tests, derive IDs uniquely (e.g. atomic counter) instead of constants"],"tags":["go","history-service","task-queue","duplicate-id","panic"],"backgroundTag":"duplicate-reader-id","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}