nautechsystems/nautilus_trader · error
Python on_dispose failed: {e}
Error message
Python on_dispose failed: {e} What it means
This error wraps any Python exception raised by the strategy's user-implemented `on_dispose()` callback. The Rust `DataActor::on_dispose` dispatches to the Python instance via `call_method0(py, "on_dispose")` (crates/trading/src/python/strategy.rs:363-368) and re-wraps any `PyErr` as `anyhow::anyhow!("Python on_dispose failed: {e}")`. Disposal is the final teardown of the actor, so a failure here can leak resources or leave the engine unable to cleanly remove the strategy.
Source
Thrown at crates/trading/src/python/strategy.rs:1081
fn on_stop(&mut self) -> anyhow::Result<()> {
self.dispatch_on_stop()
.map_err(|e| anyhow::anyhow!("Python on_stop failed: {e}"))
}
fn on_resume(&mut self) -> anyhow::Result<()> {
self.dispatch_on_resume()
.map_err(|e| anyhow::anyhow!("Python on_resume failed: {e}"))
}
fn on_reset(&mut self) -> anyhow::Result<()> {
self.dispatch_on_reset()
.map_err(|e| anyhow::anyhow!("Python on_reset failed: {e}"))
}
fn on_dispose(&mut self) -> anyhow::Result<()> {
self.dispatch_on_dispose()
.map_err(|e| anyhow::anyhow!("Python on_dispose failed: {e}"))
}
fn on_degrade(&mut self) -> anyhow::Result<()> {
self.dispatch_on_degrade()
.map_err(|e| anyhow::anyhow!("Python on_degrade failed: {e}"))
}
fn on_fault(&mut self) -> anyhow::Result<()> {
self.dispatch_on_fault()
.map_err(|e| anyhow::anyhow!("Python on_fault failed: {e}"))
}
fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {
self.dispatch_on_save()
.map_err(|e| anyhow::anyhow!("Python on_save failed: {e}"))
}
fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {View on GitHub (pinned to 18893faf8b)
Solutions
- Read the chained Python traceback to find the raising line in `on_dispose`.
- Wrap each teardown step in `try/except` so a single failed step doesn't abort the rest of disposal.
- Track which resources you actually opened and only release those (or use `contextlib.suppress` / null checks).
- Make `on_dispose` idempotent and verify it under the engine's shutdown path in tests.
Example fix
// before (strategy.py)
def on_dispose(self):
self._file.close() # raises if file was never opened or already closed
// after
def on_dispose(self):
if self._file is not None and not self._file.closed:
self._file.close()
self._file = None Defensive patterns
Strategy: try-catch
Type guard
def safe_close(resource) -> None:
if resource is None:
return
close = getattr(resource, "close", None)
if callable(close) and not getattr(resource, "closed", False):
close() Try / catch
def on_dispose(self):
try:
safe_close(getattr(self, "_file", None))
except Exception as e:
self.log.warning(f"dispose cleanup issue: {e}") # never re-raise in dispose Prevention
- Track which resources you opened and only release those in `on_dispose`.
- Make `on_dispose` idempotent; it can be called during teardown and test cleanup.
- Swallow-and-log cleanup errors in dispose; re-raising there leaves the engine in a bad state.
- Use context managers internally so resources close themselves even if dispose logic is skipped.
When it happens
Trigger: Calling `strategy.dispose()` (node shutdown, removing the strategy from a trader, or test teardown) when the Python subclass's `on_dispose()` raises: e.g. closing connections already closed, deleting temp files that no longer exist, or an unhandled exception in user teardown code.
Common situations: Interpreter shutdown ordering issues (Python objects half-torn-down when dispose runs), custom resources (files, sockets, threads) whose cleanup assumes they were opened, or dispose being called twice.
Related errors
- Python on_dispose failed: {e}
- Python on_start failed: {e}
- Python on_stop failed: {e}
- Python on_resume failed: {e}
- Python on_reset failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/16942c9c0555079f.
Report an issue: GitHub.