{"record":{"id":"e7e8be193cc9f5be","repo":"go-delve/delve","slug":"threadupdater-add-after-finish","errorCode":null,"errorMessage":"threadUpdater: Add after Finish","messagePattern":"threadUpdater: Add after Finish","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/proc/gdbserial/gdbserver.go","lineNumber":1366,"sourceCode":"// FollowExec enables (or disables) follow exec mode\nfunc (p *gdbProcess) FollowExec(bool) error {\n\treturn errors.New(\"follow exec not supported\")\n}\n\ntype threadUpdater struct {\n\tp    *gdbProcess\n\tseen map[int]bool\n\tdone bool\n}\n\nfunc (tu *threadUpdater) Reset() {\n\ttu.done = false\n\ttu.seen = nil\n}\n\nfunc (tu *threadUpdater) Add(threads []string) error {\n\tif tu.done {\n\t\tpanic(\"threadUpdater: Add after Finish\")\n\t}\n\tif tu.seen == nil {\n\t\ttu.seen = map[int]bool{}\n\t}\n\tfor _, threadID := range threads {\n\t\tb := threadID\n\t\tif period := strings.Index(b, \".\"); period >= 0 {\n\t\t\tb = b[period+1:]\n\t\t}\n\t\tn, err := strconv.ParseUint(b, 16, 32)\n\t\tif err != nil {\n\t\t\treturn &GdbMalformedThreadIDError{threadID}\n\t\t}\n\t\ttid := int(n)\n\t\ttu.seen[tid] = true\n\t\tif _, found := tu.p.threads[tid]; !found {\n\t\t\ttu.p.threads[tid] = &gdbThread{ID: tid, strID: threadID, p: tu.p}\n\t\t}","sourceCodeStart":1348,"sourceCodeEnd":1384,"githubUrl":"https://github.com/go-delve/delve/blob/a23773e6c31361e43246bc43a424ee009679b174/pkg/proc/gdbserial/gdbserver.go#L1348-L1384","documentation":"threadUpdater tracks which gdbserial threads have been seen and reports new OS-thread-to-goroutine mappings. Its lifecycle is: Reset/Add(...)/Finish; after Finish() is called (done=true) the updater is considered complete and adding more threads is a protocol/state bug, so Add panics. In gdbserver.go the interrupt-and-wait flow can finish the updater and then receive another stop packet containing thread lists.","triggerScenarios":"Calling Add after Finish on a threadUpdater — e.g. when the gdbserial target delivers an additional stop/reply packet containing a thread list after the wait loop already finished the updater (waitTimeout paths, multiple stops racing during interrupt).","commonSituations":"Racing stop notifications from a remote target (lldb-server/debugserver/rr) delivering thread info late; timeouts in waitForStop causing Finish, followed by a late thread list; flaky remote debugging sessions over slow connections.","solutions":["Update the caller so it never calls Add after Finish (finish the updater only once all stop packets for the wait are consumed)","Make Add tolerant: return an error or ignore additions after done instead of panicking","Capture verbose gdbserial logs (-log-output gdbserial) and reproduce to see which packet ordering triggers the late Add; then fix the wait loop"],"exampleFix":"// before\nfunc (tu *threadUpdater) Add(threads []string) error {\n\tif tu.done {\n\t\tpanic(\"threadUpdater: Add after Finish\")\n\t}\n// after\nfunc (tu *threadUpdater) Add(threads []string) error {\n\tif tu.done {\n\t\treturn errors.New(\"threadUpdater: Add after Finish\")\n\t}","handlingStrategy":"try-catch","validationCode":"// Go has no pre-call check; guard by checking updater state at call sites:\nif tu.done {\n\t// late stop packet: skip Add instead of calling it after Finish\n} else {\n\tif err := tu.Add(threads); err != nil { /* handle */ }\n}","typeGuard":null,"tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\tif s, ok := r.(string); ok && strings.Contains(s, \"threadUpdater: Add after Finish\") {\n\t\t\t// late stop packet: ignore thread additions\n\t\t\treturn\n\t\t}\n\t\tpanic(r)\n\t}\n}()","preventionTips":["Finish the threadUpdater only after all stop packets of a wait are consumed","Enable gdbserial logging (-log-output gdbserial) to diagnose late/interleaved stop packets","Change Add after Finish to return an error instead of panicking in downstream patches","Add race tests with multiple stop notifications during interrupt handling"],"tags":["gdbserial","thread-tracking","race","panic"],"backgroundTag":"out-of-order-stop-packet","analyzedSha":"a23773e6c31361e43246bc43a424ee009679b174","analyzedAt":"2026-08-31T15:12:45.221Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}