skip to content

In a goroutine dump, what does the header `goroutine 4821 [chan send, 47 minutes]:` tell you?

level: middleimportance: should knowfreq 42%

answer

  1. three fields on one header line
  2. the number is not a thread id
  3. the bracketed word is why it parked
  4. the duration is filtered by a threshold
  5. the last line names who spawned it

basics

~20 s

4821 is that goroutine's runtime id, chan send is the reason the runtime parked it, and 47 minutes is how long it has been blocked there — a figure printed only after roughly a minute of waiting.

solid answer

~50 s

Three fields. `4821` is the runtime's bookkeeping id for the goroutine: unique within the process, unstable across runs, and useful only for cross-referencing inside this one dump — Go exposes no API that takes it. `chan send` is the wait reason the runtime recorded when it parked the goroutine; other blocks in the same dump will show `chan receive`, `select`, `IO wait`, `sleep`, `syscall` or a reason naming the specific synchronisation primitive. `47 minutes` is how long it has been in that state, and the runtime prints it only once the wait has passed roughly a minute — so its mere presence is a signal. In a service whose requests finish in milliseconds, a goroutine parked on a channel send for 47 minutes is not working; it is stuck. Below the header come the stack frames, innermost first, and then a `created by` line naming the function that ran the `go` statement.

code

text · 7 lines
text
goroutine 4821 [chan send, 47 minutes]:
main.(*subscriber).push(0xc000112000, 0xc0001a4000)
	/srv/stream/subscriber.go:88 +0x7c
main.(*server).streamLoop(0xc0000b4000, 0xc000112000)
	/srv/stream/server.go:142 +0x5e
created by main.(*server).Subscribe in goroutine 61
	/srv/stream/server.go:140 +0x11d

go deeper

for a junior

Recall the shape of one block: an id, a state in brackets, then the stack with the innermost frame first. Knowing that the bracketed word is the reason the goroutine is parked is most of the answer.

for a middle

Explain every field and name several states you would expect to see in a real dump. Be ready to say why a minutes figure shows up on some blocks and not on others.

for a senior

Use the fields as evidence rather than trivia: a long wait at a call site that should never park, repeated across thousands of identical blocks, all traced to one created-by line. Show that you read a dump as a population.

for a principal

Set the expectation for what on-call must capture before mitigating, and decide whether raw dumps — which carry source paths, function names and internal structure — may be pasted into tickets and vendor support threads.

## The anatomy of one block A `?debug=2` goroutine dump is a sequence of blocks, one per goroutine, in the same format the runtime prints when an unrecovered panic kills a process. A block looks like this: ``` goroutine 4821 [chan send, 47 minutes]: main.(*subscriber).push(0xc000112000, 0xc0001a4000) /srv/stream/subscriber.go:88 +0x7c main.(*server).streamLoop(0xc0000b4000, 0xc000112000) /srv/stream/server.go:142 +0x5e created by main.(*server).Subscribe in goroutine 61 /srv/stream/server.go:140 +0x11d ``` Everything you need to classify this goroutine is on the first line and the last. ## The id `4821` is the runtime's internal identifier for the goroutine. It is unique among live goroutines in this process and it is monotonic, so a high id means "created late" — mildly useful when you want to know whether a population of stuck goroutines is old or fresh. It is not an OS thread id: many goroutines share one thread and a goroutine may run on different threads over its life. It is also not stable across runs, and there is deliberately no exported API that accepts one. Its real use is cross-referencing *within the dump*: the `created by ... in goroutine 61` line lets you walk from a leaked goroutine back to the goroutine that spawned it, and from there up the chain. ## The state The word in brackets is the **wait reason** the runtime recorded when it parked this goroutine. The ones you meet constantly: - `chan send` — blocked trying to send on a channel whose buffer is full, or unbuffered with no receiver ready. - `chan receive` — blocked waiting for a value on a channel nobody has sent to. - `select` — blocked in a `select` where no case is ready and there is no `default`. - `IO wait` — parked in the runtime's network poller waiting for a socket to become readable or writable. This is the normal resting state of an idle connection handler, not a defect. - `sleep` — inside `time.Sleep`. - `syscall` — executing a blocking system call on its OS thread; a read of a regular file lands here, not in `IO wait`. - `running` / `runnable` — actually executing, or waiting for a P to run on. The state answers "what would have to happen for this goroutine to continue?" — a send needs a receiver, a receive needs a sender or a close, a `select` needs any one of its cases to become ready. That is the question you are really asking when you hunt a leak. ## The duration The `, 47 minutes` suffix is not always there, and the reason is deliberate. The runtime stamps a goroutine when it enters a waiting state, and when it prints a traceback it computes the elapsed time and includes it **only if the wait has exceeded roughly a minute**. Goroutines that are running, runnable, or that parked a few seconds ago show a bare state with no duration. That threshold makes the field a filter you get for free. Scan a dump for blocks that carry a minutes figure and you have narrowed a population of ten thousand goroutines down to the ones that have been stationary for longer than any request in your service takes. Two cautions. First, it is a coarse figure in whole minutes, not a precise measurement. Second — and this is where people go wrong — a long wait is *not* the same as a leak. An idle worker parked on `chan receive` for six hours because there was no work is behaving exactly as designed; so is an accept loop and so is a background ticker. Duration narrows the field; it does not convict. ## The frames and the created-by line Under the header come the stack frames, innermost call first, each with its file, line and instruction offset. The hexadecimal arguments printed beside a function name are the raw argument words as the runtime saw them; they are occasionally useful and often misleading after inlining, so do not build an argument on them. The final `created by <function> in goroutine <id>` line is the one people underuse. It names the function that executed the `go` statement, and the goroutine that executed it. When a dump contains four thousand blocks with byte-identical stacks, that line is what turns "something is leaking" into "the writer goroutine started for each subscriber is leaking", which is a fixable statement. ## Reading a population, not a goroutine The skill this question is really testing is whether you read the dump as a census. One block tells you almost nothing. Four thousand blocks that share a stack, share a `chan send` state, all carry a minutes figure and all trace back to the same `created by` line tell you precisely where to look — and the last frame before the park tells you which channel is never being drained.

  • Why does the minutes figure appear on some goroutines in the dump and not others?
    The runtime records when a goroutine enters a waiting state and prints the elapsed time only once that wait passes roughly a minute. Anything running, runnable, or recently parked shows a bare state. That threshold is convenient — the presence of a duration is itself a filter for goroutines that have been stationary far longer than any request should take.
  • What does the state `IO wait` mean, and is it a problem?
    It means the goroutine is parked in the runtime's network poller waiting for a socket to become readable or writable, and it is the normal resting state of every idle connection handler in a server. On its own it is not a defect. It becomes interesting only when the count grows without bound, or when a read that should have a deadline has clearly been waiting for hours.
  • What is the `created by ... in goroutine 61` line worth to you?
    It names the function that ran the `go` statement, plus the goroutine that ran it. When thousands of blocks share an identical stack, that single line converts "goroutines are leaking" into "the writer we start per subscriber is leaking", which points at one function and one lifecycle to fix. It is usually the fastest route from a dump to a code change.

saying these in an interview costs you the question

  • Reads the goroutine id as an OS thread id
  • Thinks the bracketed word names the function being run
  • Assumes every goroutine in the dump carries a wait duration
  • Treats IO wait as evidence of a bug
  • Concludes a long wait alone proves a leak
  • Believes goroutine ids are stable across restarts