nautechsystems/nautilus_trader · error
Python on_dispose failed: {e}
Error message
Python on_dispose failed: {e} What it means
PyExecutionAlgorithm's DataActor on_dispose dispatches the Python `on_dispose` callback directly (no Rust pre-step); any Python exception is re-wrapped with this anyhow message. Dispose releases the component's resources, so this error means cleanup code failed in Python.
Source
Thrown at crates/trading/src/python/algorithm.rs:570
self.dispatch_no_args("on_stop")
.map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
}
fn on_resume(&mut self) -> anyhow::Result<()> {
ExecutionAlgorithm::on_resume(self)?;
self.dispatch_no_args("on_resume")
.map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
}
fn on_reset(&mut self) -> anyhow::Result<()> {
ExecutionAlgorithm::on_reset(self)?;
self.dispatch_no_args("on_reset")
.map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
}
fn on_dispose(&mut self) -> anyhow::Result<()> {
self.dispatch_no_args("on_dispose")
.map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
}
fn on_degrade(&mut self) -> anyhow::Result<()> {
self.dispatch_no_args("on_degrade")
.map_err(|e| anyhow::anyhow!("Python on_degrade failed: {e}"))
}
fn on_fault(&mut self) -> anyhow::Result<()> {
self.dispatch_no_args("on_fault")
.map_err(|e| anyhow::anyhow!("Python on_fault failed: {e}"))
}
fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
ExecutionAlgorithm::on_time_event(self, event)?;
self.dispatch_time_event(event)
.map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
}
View on GitHub (pinned to 18893faf8b)
Solutions
- Read the chained `{e}` traceback and fix the Python on_dispose implementation.
- Make dispose idempotent (set released handles to None and check before use).
- Ensure dispose is called at most once per lifecycle; track a disposed flag if needed.
- Verify the zero-arg `def on_dispose(self)` signature.
Example fix
// before
def on_dispose(self):
self.cache.close() # second call raises
// after
def on_dispose(self):
if self.cache is not None:
self.cache.close()
self.cache = None Defensive patterns
Strategy: try-catch
Validate before calling
# Python: guard against double dispose if getattr(self, "disposed", False): return
Try / catch
if let Err(e) = actor.on_dispose() {
log::error!("python on_dispose failed: {e:#}"); // release handles regardless
} Prevention
- Set a disposed flag and no-op subsequent on_dispose calls.
- Null out released handles so repeated cleanup cannot raise.
- Never rely on __del__ for the same resources on_dispose releases.
When it happens
Trigger: Calling on_dispose when the Python on_dispose raises — e.g. freeing resources twice, or bad attribute access during teardown.
Common situations: Double dispose when both stop and dispose paths run cleanup; dropping references already released; exceptions in __del__-like cleanup logic.
Related errors
- Python on_start failed: {e}
- Python on_stop failed: {e}
- Python on_resume failed: {e}
- Python on_reset failed: {e}
- Python on_degrade failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/cc9cf0694a23575d.
Report an issue: GitHub.