go-delve/delve · warning
thread blocked
Error message
thread blocked
What it means
errThreadBlocked is an internal sentinel error returned by waitForvContStop. When the vCont continue/step is issued and a stop packet indicates the thread could not run because it is blocked (e.g. blocked in a syscall or waiting on another thread's stop), the connection uses this error to signal that state to its caller, which typically retries or resolves the thread state.
Solutions
- Retry the continue/step — Delve handles this internally; if it surfaces, simply continue again
- Resume other threads first or use process-wide continue instead of stepping a single thread
- Check whether the target thread is stuck in a syscall and let the program progress to a runnable state
- If it recurs persistently, restart the session; some stub versions have blocked-thread reporting bugs
Defensive patterns
Strategy: retry
Try / catch
if err := sess.Step(); err != nil {
if strings.Contains(err.Error(), "thread blocked") {
// thread is in a blocking syscall; wait and retry or continue all threads
time.Sleep(100 * time.Millisecond)
err = sess.Continue()
}
} Prevention
- Prefer process-wide continue over stepping individual threads parked in syscalls
- Step on threads in runnable user code, not inside blocking syscalls
- Update lldb-server/debugserver to reduce all-stop scheduling quirks
When it happens
Trigger: Calling ContinueOnce/step on the gdbserial backend when waitForvContStop reads a stop reply showing the thread is blocked rather than truly stopped at a signal; common with all-stop mode where one thread cannot resume until others stop.
Common situations: Stepping a thread that is parked in a blocking syscall (read/write/futex); debugging multithreaded programs via lldb-server/debugserver where thread scheduling interlocks; single-thread resume requests during process-wide stops.
Related errors
- direction change with internal breakpoints
- software exception
- allocating a literal of type
- array too long for comparison
- bad access
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/43a4808d64652615.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/proc/gdbserial/gdbserver_conn.go:692
return nil
case childSignal: // stop on debugserver but SIGCHLD on lldb-server/linux
if conn.isDebugserver {
return nil
}
case debugServerTargetExcBadAccess, debugServerTargetExcBadInstruction, debugServerTargetExcArithmetic, debugServerTargetExcEmulation, debugServerTargetExcSoftware, debugServerTargetExcBreakpoint:
if ignoreFaultSignal {
return nil
}
return machTargetExcToError(sig)
default:
// delay propagation of any other signal to until after the stepping is done
th.sig = sig
sig = 0
}
}
}
var errThreadBlocked = errors.New("thread blocked")
func (conn *gdbConn) waitForvContStop(context, threadID string, tu *threadUpdater) (stopPacket, error) {
count := 0
failed := false
for {
conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval))
resp, err := conn.recv(nil, context, false)
conn.conn.SetReadDeadline(time.Time{})
if neterr, isneterr := err.(net.Error); isneterr && neterr.Timeout() {
// Debugserver sometimes forgets to inform us that inferior stopped,
// sending this status request after a timeout helps us get unstuck.
// Debugserver will not respond to this request unless inferior is
// already stopped.
if conn.isDebugserver {
conn.send([]byte("$?"))
}
if count > 1 && context == "singlestep" {
failed = trueView on GitHub (pinned to a23773e6c3)