tracel-ai/burn · error

AsyncPolicy should be able to send policy state.

Error message

AsyncPolicy should be able to send policy state.

What it means

AsyncPolicy::update sends a PolicyUpdate message to the autobatcher thread and panics if the send fails. A failed send means the receiving side is closed — the inference thread has already exited, typically because it panicked on an earlier message or saw RecvError after all AsyncPolicy handles were dropped. The policy update is silently lost as a panic instead of being applied.

Source

Thrown at crates/burn-rl/src/policy/async_policy.rs:300

        let (action_sender, action_receiver) = std::sync::mpsc::channel();
        let item = ActionItem {
            sender: action_sender,
            inference_state: states,
            deterministic,
        };
        self.inference_state_sender
            .send(InferenceMessage::ActionMessage(item))
            .expect("should be able to send message to inference_server.");
        let action = action_receiver
            .recv()
            .expect("AsyncPolicy should receive queued actions.");
        (action.action, action.context)
    }

    fn update(&mut self, update: Self::PolicyState) {
        self.inference_state_sender
            .send(InferenceMessage::PolicyUpdate(update))
            .expect("AsyncPolicy should be able to send policy state.")
    }

    fn state(&self) -> Self::PolicyState {
        let (sender, receiver) = mpsc::channel();
        self.inference_state_sender
            .send(InferenceMessage::PolicyRequest(sender))
            .expect("should be able to send message to inference_server.");
        receiver
            .recv()
            .expect("AsyncPolicy should be able to receive policy state.")
    }

    fn to_device(self, device: &Device) -> Self {
        self.inference_state_sender
            .send(InferenceMessage::ToDevice(device.clone()))
            .expect("AsyncPolicy should be able to send policy state.");
        self
    }

View on GitHub (pinned to d16f7ba2ed)

Solutions

  1. Fix the original autobatcher-thread failure visible in logs before this call.
  2. Recreate the AsyncPolicy (and re-apply the missed update) with AsyncPolicy::new after confirming the thread is dead.
  3. Ensure inner_policy.update/PolicyState records match the running policy's device and architecture to avoid server-thread panics.
  4. For robustness, have the server thread log-and-continue on recoverable message errors rather than unwinding.

Example fix

// before
self.inference_state_sender
    .send(InferenceMessage::PolicyUpdate(update))
    .expect("AsyncPolicy should be able to send policy state.")
// after
if let Err(err) = self.inference_state_sender.send(InferenceMessage::PolicyUpdate(update)) {
    log::error!("Failed to deliver policy update; inference server is down: {}", err);
    *self = AsyncPolicy::new(self.autobatch_size, self.rebuild_inner_policy());
    // re-apply the update on the new server
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the server is responsive before pushing a policy update
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    let mut p = policy.clone();
    let _ = p.forward(sample_observation.clone());
})).map_err(|_| anyhow::anyhow!("inference server dead; cannot apply update"))?;

Try / catch

if let Err(_panicked) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| policy.update(new_state))) {
    log::error!("policy update lost: inference server is down; rebuilding and re-applying");
    policy = AsyncPolicy::new(autobatch_size, inner_policy.clone());
    policy.update(new_state);
}

Prevention

When it happens

Trigger: Calling update() after the server thread died: an earlier panic in the autobatcher loop (e.g. inside update_policy's flush, or inner_policy.update panicking on incompatible records), or the training driver dropped all other AsyncPolicy clones causing thread exit while a learner still calls update.

Common situations: Learner/actor split setups where the actor processes die and the learner's update() then panics on the dead channel; optimizer step errors (record mismatch, wrong device) crashing the server thread before subsequent updates arrive.

Related errors


AI-assisted analysis of tracel-ai/burn@d16f7ba2ed (2026-09-05). Data as JSON: /api/errors/2ccce126544a648c. Report an issue: GitHub.