mirror of
https://github.com/openai/codex.git
synced 2026-04-28 10:21:06 +03:00
3.6 KiB
3.6 KiB
DOs
- Move the sender: Move
mpsc::Senderinto the producer task so it drops when the task ends, allowing the receiver to finish.
use tokio::sync::mpsc;
let (tx, mut rx) = mpsc::channel::<String>(16);
let stdin_task = tokio::spawn(async move {
let _ = tx.send("line".into()).await;
}); // tx drops here
let processor = tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
// handle msg
} // exits when all senders drop
});
- Drop before join: If the parent holds an extra sender (e.g., for setup), drop it explicitly before awaiting joins.
let (tx, mut rx) = mpsc::channel::<()>(1);
let tx_parent = tx.clone();
let t = tokio::spawn(async move {
let _ = tx.send(()).await;
});
// Critical: close the channel on the parent side
drop(tx_parent);
let _ = tokio::join!(t);
- Rely on move capture: Let
async movecapture needed variables; avoid needless rebindings insidetokio::spawn.
let (tx, _rx) = tokio::sync::mpsc::channel::<String>(16);
let task = tokio::spawn(async move {
let _ = tx.send("ok".into()).await; // use tx directly
});
- Bound receiver loops: Use
while let Some(...)to exit cleanly when the channel closes.
let processor = tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
// process msg
} // channel closed => loop ends
});
- Clone intentionally: Only clone when you truly need multiple producers, and ensure each clone is dropped.
let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(16);
let tx2 = tx.clone();
let a = tokio::spawn(async move { let _ = tx.send("a".into()).await; });
let b = tokio::spawn(async move { let _ = tx2.send("b".into()).await; });
let _ = tokio::join!(a, b);
while let Some(_msg) = rx.recv().await {}
DON’Ts
- Clone inside the task needlessly: Don’t create an extra
Senderclone in a spawned task when the closure can move the original.
// Anti-pattern: leaves an extra Sender alive outside the task
let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(16);
let stdin_task = tokio::spawn({
let tx_clone = tx.clone(); // unnecessary clone
async move {
let _ = tx_clone.send("line".into()).await;
}
});
// If `tx` remains alive here, `rx` may never see closure.
- Keep spare senders across join: Don’t hold a
Senderin the parent scope while awaiting task completion.
let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(1);
let tx_main = tx.clone();
let t = tokio::spawn(async move { let _ = tx.send(()).await; });
// Anti-pattern: join while `tx_main` is still alive
let _ = tokio::join!(t); // receiver may never finish
- Self-assign in closures: Don’t write
let tx = tx;inside thetokio::spawnblock; remove the line and rely on move capture.
// Anti-pattern
let task = tokio::spawn({
let tx = tx; // redundant; just omit this line
async move { /* use tx */ }
});
- Create accidental long-lived owners: Don’t store the sender in a struct or outer variable that outlives the worker tasks unless you explicitly manage its drop.
struct State { tx: tokio::sync::mpsc::Sender<String> }
let state = State { tx }; // long-lived owner
// … later …
let _ = tokio::join!(worker1, worker2); // may hang if `state.tx` is still alive
- Assume receiver stops without closure: Don’t use unbounded loops that ignore channel closure; always handle
Nonefromrecv().
// Anti-pattern
loop {
// `recv()` returns Option<T>; ignoring None risks hangs or errors
if let Some(msg) = rx.recv().await {
// process
} else {
break; // handle closure explicitly
}
}