What does the traceback header `goroutine 84 [chan receive, 12 minutes]:` tell you about that goroutine?
answer
- three separate facts on one line
- the bracket is a wait reason
- ids only ever increase
- duration appears only after minutes
- blocked is not the same as broken
basics
~20 sIt is goroutine number 84, the runtime parked it while it was receiving from a channel, and it has been blocked for about twelve minutes. The duration is printed only once a goroutine has been waiting for minutes.
solid answer
~50 sThree facts. The id, 84, comes from an ever-increasing counter that is never reused, so a low id means an old goroutine and a run of consecutive ids means a batch created together; it is not an OS thread id. The bracketed word is the wait reason the runtime recorded when it parked the goroutine — here it is blocked receiving from a channel. The vocabulary is small: `running`, `runnable`, `syscall`, `IO wait` (parked on the network poller), `chan receive`, `chan send`, `select`, `sleep`, `semacquire`, and refinements such as `chan receive (nil chan)`. The trailing duration only appears once the wait has run into minutes, which is what makes it valuable. The reading rule matters most: a wait reason says where a goroutine is parked, not that anything is wrong — idle workers blocking on a receive is normal.
code
text · 5 linesgoroutine 84 [chan receive, 12 minutes]:
main.(*Consumer).worker(0xc0000ac000)
/srv/worker/consume.go:74 +0x8c
created by main.(*Consumer).start in goroutine 17
/srv/worker/consume.go:66 +0x11dgo deeper
Be able to name the three parts out loud: which goroutine, what it is waiting on, and how long. Knowing that a blocked goroutine is normal is most of the value here.
Explain the wait-reason vocabulary and what each implies about where the goroutine is parked, including the difference between IO wait, syscall and runnable.
Show the judgment layer: a state plus a duration plus the number of goroutines sharing it is a finding, while a state on its own is not, and say what you would check next.
Decide what the team treats as an alertable signal from goroutine states and counts, and how much runtime detail is worth exposing in dashboards versus read at crash time.
## Anatomy of the header line Every goroutine in a traceback opens with a line of the form ``` goroutine 84 [chan receive, 12 minutes]: ``` There are three pieces of information in it, and each answers a different question. **`goroutine 84` — which one.** Goroutine ids come from a counter that only increases, and they are not reused: a fresh goroutine always gets a number nobody has had. So a low id was created early — `goroutine 1` is the goroutine running `main` — and a block of near-consecutive ids means those goroutines were created together, in a burst. The id is purely a runtime bookkeeping number: it is not an OS thread id, it means nothing outside the process, and Go deliberately offers no supported API to read your own. Its value to a reader is ordering and cross-referencing — a `created by ... in goroutine 17` line elsewhere in the dump points at a specific parent. **`chan receive` — where it is parked.** This is the *wait reason* the runtime recorded when it parked the goroutine. The vocabulary is small and worth knowing: | state | what it means | |---|---| | `running` | executing right now (in a crash dump, usually the goroutine that crashed) | | `runnable` | ready to run, waiting for a processor to pick it up | | `syscall` | blocked inside a system call on an OS thread | | `IO wait` | parked on the network poller, waiting for a socket to become readable or writable | | `chan receive` / `chan send` | blocked on a channel operation | | `select` | blocked in a `select` with no ready case | | `sleep` | inside `time.Sleep` | | `semacquire` | contending for a lock or waiting on a counter | | `GC assist wait` | throttled by the collector while it allocates | Parenthesised refinements appear too — `chan receive (nil chan)` tells you the channel operand was nil, which blocks forever by definition and is a completely different bug from a channel with no sender. A goroutine pinned to its thread prints `locked to thread`. **`12 minutes` — how long.** The runtime records when it parked the goroutine and adds the elapsed time to the header once the wait has run into minutes; short waits print no duration at all. This is the single most useful field in a large dump, because it converts a state into a judgment. Three hundred goroutines in `chan receive` is what a healthy idle worker pool looks like. Three hundred goroutines in `chan receive` for forty minutes, in a service whose queue is known to be busy, is a finding. ## The reading rule **A wait reason describes a location, not a fault.** Blocking is the normal condition of most goroutines in a long-lived Go program: workers wait for work, servers wait for connections, tickers sleep. Nothing in the header says whether that is correct. What makes a state interesting is the combination of the reason, the duration, the count of goroutines sharing it, and what the code is supposed to be doing at that moment. A candidate who says "they're blocked on a channel, that's the bug" has not read the dump; a candidate who says "they're blocked on a channel, which is expected — what is odd is that it has been forty minutes and there is no sender anywhere in this dump" has. ## Two distinctions people get wrong `IO wait` versus `syscall`. `IO wait` means the goroutine handed its file descriptor to the runtime's network poller and parked; no OS thread is tied up, and this is what a goroutine blocked reading a socket normally looks like. `syscall` means the goroutine is sitting inside a blocking system call on a real thread — ordinary file reads and `cgo` calls land here — and while it does, that thread is consumed. A hundred goroutines in `IO wait` is cheap; a hundred in `syscall` costs a hundred threads. `runnable` versus `running`. `running` means executing at the instant of the dump. `runnable` means the goroutine has work to do and is queued, waiting for a processor. A dump thick with `runnable` goroutines is a CPU-saturation signal, not a blocking one. Note also that in a crash dump a goroutine executing on another thread often cannot have its stack walked safely, and prints `goroutine running on other thread; stack unavailable` in place of frames.
- How is a goroutine in `IO wait` different from one in `syscall`?`IO wait` means the goroutine handed its file descriptor to the runtime's network poller and parked, so no OS thread is tied up — that is what a goroutine blocked on a socket normally looks like. `syscall` means it is sitting inside a blocking system call on a real thread, which ordinary file reads and cgo calls do, and that thread is consumed while it waits.
- Does a goroutine's id tell you anything useful when reading a dump?Yes, ordering. Ids come from a counter that only increases and are never reused, so a lower number was created earlier and goroutine 1 is running `main`. A block of near-consecutive ids means a burst created together; ids spread across the whole range mean accumulation over the process's life. They mean nothing to the operating system.
- In a crash dump, why do some goroutines print no frames at all?A goroutine executing on another thread at the instant of the dump usually cannot have its stack walked safely, so the runtime prints `goroutine running on other thread; stack unavailable` in place of frames. Very deep stacks are also truncated, with a line saying additional frames were elided.
saying these in an interview costs you the question
- Reads any blocked state as evidence of a bug
- Thinks the minutes figure is CPU time consumed
- Treats the goroutine id as an OS thread id
- Assumes IO wait means disk input and output
- Believes goroutine ids are recycled so ordering means nothing