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.
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)
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
- hardware breakpoints exhausted
- break on read only not supported
- ErrCouldNotDetermineRelocation
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/43a4808d64652615.
Report an issue: GitHub.