tracel-ai/burn · critical
Autobatcher should be able to send current policy state.
Error message
Autobatcher should be able to send current policy state.
What it means
This panic fires inside the dedicated autobatcher thread when it handles a PolicyRequest: it calls autobatcher.state() and tries to send the resulting PolicyState back to the caller over the one-shot mpsc channel. The expect triggers when that receiver has already been dropped — i.e. the AsyncPolicy::state() caller gave up (its thread ended or the channel was dropped) before the autobatcher got around to answering. Because the expect is inside the server loop, this panic kills the whole inference thread, after which every other AsyncPolicy method will also panic.
Source
Thrown at crates/burn-rl/src/policy/async_policy.rs:220
///
/// # Arguments
///
/// * `autobatch_size` - Number of observations to accumulate before running a pass of inference.
/// * `inner_policy` - The policy used to take actions.
pub fn new(autobatch_size: usize, inner_policy: P) -> Self {
let (sender, receiver) = std::sync::mpsc::channel();
let mut autobatcher = PolicyInferenceServer::new(autobatch_size, inner_policy.clone());
spawn(move || {
loop {
match receiver.recv() {
Ok(msg) => match msg {
InferenceMessage::ActionMessage(item) => autobatcher.push_action(item),
InferenceMessage::ForwardMessage(item) => autobatcher.push_logits(item),
InferenceMessage::PolicyUpdate(update) => autobatcher.update_policy(update),
InferenceMessage::ToDevice(device) => autobatcher.policy_to_device(&device),
InferenceMessage::PolicyRequest(sender) => sender
.send(autobatcher.state())
.expect("Autobatcher should be able to send current policy state."),
InferenceMessage::IncrementAgents(num) => autobatcher.increment_agents(num),
InferenceMessage::DecrementAgents(num) => autobatcher.decrement_agents(num),
},
Err(err) => {
log::error!("Error in AsyncPolicy : {}", err);
break;
}
}
}
});
Self {
inference_state_sender: sender,
}
}
/// Increment the number of agents using the inference server.
pub fn increment_agents(&self, num: usize) {View on GitHub (pinned to d16f7ba2ed)
Solutions
- Ensure the thread that calls state() blocks on receiver.recv() until it gets a reply — do not drop the AsyncPolicy handle or unwind before recv() returns.
- Wrap the autobatcher-side send so a dead reply channel does not kill the inference thread: replace .expect(...) with if let Err(err) = sender.send(...) { log::warn!(...); } since a dropped receiver is non-fatal for the server.
- Check for earlier panics/logs from the autobatcher thread (e.g. inner_policy.state() failing) that delay or abort the reply.
- If using timeouts around state(), poll recv_timeout on the caller side but keep the receiver alive, or re-issue the PolicyRequest after a timeout instead of abandoning it.
Example fix
// before
InferenceMessage::PolicyRequest(sender) => sender
.send(autobatcher.state())
.expect("Autobatcher should be able to send current policy state."),
// after
InferenceMessage::PolicyRequest(sender) => {
if let Err(err) = sender.send(autobatcher.state()) {
log::warn!("Policy requester dropped before receiving state: {}", err);
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Caller side: keep the receiver alive until the reply arrives let (tx, rx) = std::sync::mpsc::channel(); policy_handle_send(PolicyRequest(tx)); // must not drop tx/rx before recv assert!(!std::thread::current().is_panicking());
Try / catch
let state = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| policy.state()))
.map_err(|_| anyhow::anyhow!("inference server thread died while snapshotting state"))?; Prevention
- Never drop the AsyncPolicy handle or return from the calling thread before receiver.recv() completes.
- Avoid wrapping state() in fire-and-forget tasks or timeouts that abandon the reply channel.
- Replace the autobatcher-side reply .expect with a logged error so a dropped receiver cannot kill the inference thread.
- Monitor logs for earlier autobatcher-thread panics; they are the usual precursor to reply-channel failures.
When it happens
Trigger: Calling AsyncPolicy::state() from a scope where the returned mpsc::Receiver can be dropped before the autobatcher processes the PolicyRequest — e.g. calling state() on a cloned AsyncPolicy inside a short-lived worker thread that is cancelled/timed out, or wrapping state() in a timeout that abandons the receiver while the inner policy's state() call (possibly a device copy) is still pending.
Common situations: RL training loops that snapshot policy state from rayon/tokio worker tasks with timeouts; dropping an AsyncPolicy clone while a PolicyRequest is still queued behind a large action batch, so the reply channel dies before the server reaches the request; panics or unwinding in the calling thread between send and recv.
Related errors
- Can send message to autobatcher.
- Should be able to send message to inference_server
- AsyncPolicy should be able to receive policy state.
- AsyncPolicy should receive queued probabilities.
- should be able to send message to inference_server.
AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05).
Data as JSON: /api/errors/776d7954f76f8da6.
Report an issue: GitHub.