#283 Jul 26, 2026

283. recv_timeout — Wait for a Message, But Not Forever

rx.recv() blocks until a message arrives — and if the producer hangs, so do you. recv_timeout puts a deadline on the wait and tells you why it ended.

Bite 282 bounded the queue so a fast producer can’t outrun the consumer. The mirror problem: a consumer stuck on recv() because the producer stalled — a hung network call, a deadlock, a job that just takes long. recv() gives you no way to bail out, log progress, or check a shutdown flag:

1
let msg = rx.recv().unwrap(); // producer hangs → you hang

recv_timeout(d) waits at most d, then returns an error you can act on:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::sync::mpsc::{self, RecvTimeoutError};
use std::time::Duration;
use std::thread;

let (tx, rx) = mpsc::channel();

thread::spawn(move || {
    thread::sleep(Duration::from_millis(50)); // slow worker
    tx.send("result").unwrap();
});

// worker isn't done yet — we get control back instead of hanging
assert_eq!(
    rx.recv_timeout(Duration::from_millis(5)),
    Err(RecvTimeoutError::Timeout)
);

// try again with a longer deadline — message arrives
assert_eq!(
    rx.recv_timeout(Duration::from_secs(2)).unwrap(),
    "result"
);

The error type is the useful part. Timeout means “nothing yet, sender still alive — worth retrying.” Disconnected means “every sender is gone — no message will ever come, stop waiting”:

1
2
3
4
5
// the worker thread finished, dropping its Sender
assert_eq!(
    rx.recv_timeout(Duration::from_secs(2)),
    Err(RecvTimeoutError::Disconnected)
);

That distinction is what makes the classic supervision loop work — wake up periodically, check a shutdown flag, and still exit promptly when the workers are done:

1
2
3
4
5
6
7
8
9
loop {
    match rx.recv_timeout(Duration::from_millis(100)) {
        Ok(job) => handle(job),
        Err(RecvTimeoutError::Timeout) => {
            if shutdown_requested() { break; }
        }
        Err(RecvTimeoutError::Disconnected) => break,
    }
}

Rule of thumb: recv() only when you’re certain the sender side can’t stall. Anywhere a producer might hang — or you need to interleave waiting with other checks — recv_timeout keeps the consumer in control.

← Previous 282. mpsc::sync_channel — A Channel That Pushes Back When the Consumer Falls Behind Next → 284. try_recv — Poll the Channel Without Stalling the Loop