{"record":{"id":"4763cd1d26ccd9e9","repo":"temporalio/temporal","slug":"can-not-invoke-method-on-destroyed-queue-slice","errorCode":null,"errorMessage":"Can not invoke method on destroyed queue slice","messagePattern":"Can not invoke method on destroyed queue slice","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/history/queues/slice.go","lineNumber":447,"sourceCode":"\n\ts.iterators = []Iterator{\n\t\tNewIterator(s.paginationFnProvider, s.scope.Range),\n\t}\n\ts.clear()\n\n\ts.monitor.SetSlicePendingTaskCount(s, len(s.pendingExecutables))\n}\n\nfunc (s *SliceImpl) destroy() {\n\ts.destroyed = true\n\ts.iterators = nil\n\ts.executableTracker = nil\n\ts.monitor.RemoveSlice(s)\n}\n\nfunc (s *SliceImpl) stateSanityCheck() {\n\tif s.destroyed {\n\t\tpanic(\"Can not invoke method on destroyed queue slice\")\n\t}\n}\n\nfunc (s *SliceImpl) newSlice(\n\tscope Scope,\n\titerators []Iterator,\n\ttracker *executableTracker,\n) *SliceImpl {\n\tslice := &SliceImpl{\n\t\tpaginationFnProvider: s.paginationFnProvider,\n\t\texecutableFactory:    s.executableFactory,\n\t\tscope:                scope,\n\t\titerators:            iterators,\n\t\texecutableTracker:    tracker,\n\t\tmonitor:              s.monitor,\n\t\tmaxPredicateSizeFn:   s.maxPredicateSizeFn,\n\t\tmaxPendingKeysFn:     s.maxPendingKeysFn,\n\t\tmetricsHandler:       s.metricsHandler,","sourceCodeStart":429,"sourceCodeEnd":465,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/history/queues/slice.go#L429-L465","documentation":"SliceImpl is a task-queue slice in the history service; once destroy() runs the slice is removed from the monitor and its trackers are nil, so any further method call is a use-after-free bug. stateSanityCheck panics to fail fast instead of operating on nil iterators/trackers. It guards methods like Scope, SplitByPredicate, CanMergeWithSlice, ShrinkScope, SelectTasks, MoreTasks, TaskStats, Clear.","triggerScenarios":"Calling any public Slice method after the slice was destroyed — destroy() is invoked internally by appendMergedSlice when a merged slice's scope becomes empty, or by the queue tracker during re-bucketing; keeping a reference to a slice returned by a queue and calling methods on it after the queue merged/split slices.","commonSituations":"Holding a *SliceImpl across a queue operation that merges slices; concurrent access where one goroutine merges slices while another still uses an old reference; custom code extending the queue manager that retains slice pointers.","solutions":["Never retain Slice references across queue operations; re-fetch slices from the queue manager after any split/merge","Check slice ownership: only the owning QueueBase/tracker should call mutating methods","Serialize access — queue slices are not goroutine-safe; use the queue's operation flow","If reproducible, report with the stack trace to temporalio/temporal as an internal invariant violation"],"exampleFix":"// before: retaining a slice across a merge\nslice := queue.GetSlices()[0]\nqueue.MergeSlices(...)\nslice.ShrinkScope() // panics if slice was destroyed by merge\n// after: re-acquire current slices from the queue after mutation\nqueue.MergeSlices(...)\nfor _, slice := range queue.GetSlices() { slice.ShrinkScope() }","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isUsable(s queues.Slice) bool {\n  type alive interface{ MoreTasks() bool }\n  defer func() { recover() }()\n  _ = s.MoreTasks() // panics if destroyed\n  return true\n}","tryCatchPattern":"// Guard retained references at goroutine boundaries:\nfunc safeCall(s queues.Slice, fn func(queues.Slice)) (err error) {\n  defer func() { if r := recover(); r != nil { err = fmt.Errorf(\"slice destroyed: %v\", r) } }()\n  fn(s)\n  return nil\n}","preventionTips":["Do not retain *SliceImpl references across queue split/merge operations","Re-fetch slices from the queue manager after any mutation","Only let the owning queue tracker mutate slices","Serialize all slice access through the queue's operation flow"],"tags":["go","history-service","task-queue","use-after-destroy"],"backgroundTag":"use-after-destroy","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}