How do you unblock a goroutine that is already blocked in Read on a net.Conn?
answer
- no cancellable Read exists in Go
- deadlines cover in-flight operations too
- push the instant into the past
- safe to call from another goroutine
- Close works but spends the connection
basics
~20 sFrom another goroutine, call SetReadDeadline with an instant already in the past, such as time.Now(). Deadlines apply to in-flight operations, so the blocked Read returns a timeout error at once, and the connection stays usable if you set a future deadline afterwards.
solid answer
~40 sThere is no cancellable read in Go: `Read` on a `net.Conn` blocks until bytes arrive, the peer closes, or a deadline fires. The deadline is the interruption mechanism, because it applies to **pending** I/O and not just to operations that start later. So another goroutine calls `conn.SetReadDeadline(time.Now())` — any past instant does — and the blocked `Read` returns immediately with an error whose `Timeout()` reports true. This is safe: multiple goroutines may call `net.Conn` methods concurrently. The alternative is `Close`, which also unblocks the read but ends the connection for good and returns an error matching `net.ErrClosed` rather than a timeout. Prefer the past deadline when you want to abandon one operation and keep the connection: clear the deadline with `time.Time{}` or set a future one and it works again.
code
go · 8 lines// Called from a goroutine other than the one blocked in Read.
func abortRead(c net.Conn) error {
return c.SetReadDeadline(time.Now()) // any past instant
}
func resumeRead(c net.Conn) error {
return c.SetReadDeadline(time.Now().Add(30 * time.Second))
}go deeper
Remember that Go has no cancellable socket read, and that moving the read deadline into the past is the standard way to get a blocked reader back.
Explain that deadlines apply to pending operations, that net.Conn methods are safe to call concurrently, and how the resulting timeout error differs from the net.ErrClosed you get from Close.
Show the recovery decision: after interrupting, whether the connection can be reused depends on whether the read had already consumed part of a message. Call out the goroutine leak in the select-on-a-timer non-solution.
Decide where abandonment lives in a client library — a per-operation deadline the caller sets, or a watcher the library owns — since callers cannot retrofit it and the choice shapes every consumer's shutdown path.
## The problem A goroutine calls `c.Read(buf)` on a socket and no bytes are coming. You want it back — because the user hit Ctrl-C, because a shutdown is in progress, because a higher layer gave up. There is no `Read` variant that takes a cancellation signal, and you cannot interrupt a goroutine from outside. So what actually gets that goroutine moving? ## Deadlines apply to pending I/O The documented behaviour of the deadline methods is the key: a deadline applies to **all future and pending** I/O in that direction, not merely to the next call. Setting a deadline that is already in the past therefore does not just affect the next `Read` — it makes the one currently blocked return, right now, with an error satisfying `net.Error` whose `Timeout()` reports true and matching `errors.Is(err, os.ErrDeadlineExceeded)`. ``` // Called from any other goroutine. c.SetReadDeadline(time.Now()) ``` This is explicitly safe to do concurrently: `net.Conn` documents that multiple goroutines may invoke its methods simultaneously. You do not need a mutex between the reader and the interrupter, and you do not need to know whether the reader is currently inside `Read` or between reads — if it is between reads, the next `Read` fails immediately for the same reason. ## Why this and not Close? `Close` also unblocks a pending read, and for a hard shutdown it is the right call. The differences matter: * **Reusability.** After a past deadline, the connection is intact. Set `SetReadDeadline(time.Time{})` or a fresh future instant and reads work again. After `Close`, the connection is gone. * **The error you get.** A deadline gives a timeout error; a close gives one matching `net.ErrClosed` ("use of closed network connection") whose `Timeout()` is false. Code that retries on timeouts but not on closes depends on that difference. * **Racing readers.** Closing a descriptor that another goroutine is reading is safe in Go — the runtime's poller handles it — but it is a one-way door, and in a pooled or long-lived client you usually want to abandon one operation, not the socket. ## Restoring the connection Interrupting with a past deadline leaves a subtlety: the connection is fine, but whatever the read was in the middle of may not be. If the interrupted read had already consumed part of a framed message, the stream is misaligned and the only correct recovery is to drop the connection. If the read was idle — waiting for the first byte of the next message — you can clear the deadline and carry on. ``` c.SetReadDeadline(time.Time{}) // no deadline c.SetReadDeadline(time.Now().Add(30*time.Second)) // or a fresh budget ``` ## The shape this appears in In a wire layer for a long-lived protocol, the interrupter is usually a small watcher goroutine that waits on a stop channel and pushes the deadline into the past when it fires. It is a handful of lines, it needs no lock, and it converts "my program is stuck in a socket read" into "my read returned an error I can handle". The same mechanism is how a driver makes a blocking network round-trip abandonable at all — the socket API offers no other lever. ## Two traps First, **a future deadline does not interrupt anything now.** It applies to the pending read, but the read still waits until that instant. If you want the read back immediately, the instant must already have passed. Second, **the write side has its own deadline.** A goroutine blocked in `Write` because the peer's receive window is full is not affected by `SetReadDeadline`; it needs `SetWriteDeadline`, or `SetDeadline` to move both at once. ## Recognising the anti-patterns Two non-solutions come up often and neither works. Sending a byte to yourself on the same connection does not unblock a reader — the bytes go to the peer. And spawning a goroutine that does the read and reporting a timeout via a `select` in the caller only makes the *caller* return; the reading goroutine is still parked in the syscall and its stack, its buffer and its descriptor are still held. That is a goroutine leak dressed as a timeout, and it accumulates one leaked goroutine per abandoned operation.
- What error does the blocked Read return if the other goroutine calls Close instead?An error matching `errors.Is(err, net.ErrClosed)` — "use of closed network connection" — and its `Timeout()` reports false. That distinction matters to retry logic: a timeout may be worth retrying on a fresh connection, while a close means your own program shut the socket down deliberately.
- Why not just run the Read in its own goroutine and select on a timer in the caller?That unblocks the caller, not the read. The reading goroutine stays parked in the syscall holding its stack, its buffer and the descriptor, so every abandoned operation leaks one goroutine and the socket is never freed. Moving the deadline actually ends the read.
- Does calling SetReadDeadline concurrently with an in-flight Read need synchronisation?No. `net.Conn` documents that multiple goroutines may invoke its methods simultaneously, and the deadline methods are specified to affect pending operations. No mutex is needed between the reader and the goroutine moving the deadline.
- A goroutine is stuck in Write instead. Does a past read deadline free it?No. The read and write deadlines are independent, so a blocked `Write` — typically blocked because the peer stopped reading and the send window filled — needs `SetWriteDeadline` with a past instant, or `SetDeadline`, which moves both directions at once.
saying these in an interview costs you the question
- Claims a blocked socket read cannot be interrupted at all
- Thinks a future deadline returns the pending read immediately
- Wraps the read in a goroutine and calls the leak a timeout
- Assumes SetReadDeadline needs a mutex against the reader
- Believes Close is the only way and always spends the connection
- Expects a read deadline to free a goroutine blocked in Write