nautechsystems/nautilus_trader · error
Python on_dispose failed: {e}
Error message
Python on_dispose failed: {e} What it means
Raised by the Rust `DataActor` bridge when the Python actor's `on_dispose()` callback raises. `dispatch_on_dispose()` propagates the Python exception and it is rewrapped as this anyhow error. Dispose is the final teardown hook after stop, so failures here happen during component destruction.
Source
Thrown at crates/common/src/python/actor.rs:1040
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
- Check the embedded Python traceback for the exact raise site in on_dispose.
- Make dispose idempotent: guard closes/deletes with checks or try/except and log instead of raising.
- Ensure on_dispose does not depend on state that on_stop already released.
- Test full stop->dispose teardown to confirm cleanup is safe to run twice.
Example fix
// before (Python)
def on_dispose(self):
self.socket.close()
// after
def on_dispose(self):
if self.socket is not None:
try:
self.socket.close()
finally:
self.socket = None Defensive patterns
Strategy: try-catch
Validate before calling
# ensure teardown targets still exist before dispose assert self.socket is not None or getattr(self, 'socket', None) is None, 'socket state inconsistent'
Type guard
def closable(res):
return res is not None and hasattr(res, 'close') Try / catch
def on_dispose(self):
try:
... # final teardown
except Exception as e:
self.log.error(f"on_dispose failed: {e}")
finally:
self.socket = None Prevention
- Make on_dispose idempotent — safe to call after on_stop already cleaned up.
- Use try/finally so references are cleared even if close fails.
- Never depend in dispose on state released in earlier lifecycle stages.
- Exercise full stop->dispose in integration tests.
When it happens
Trigger: Any exception in the user's Python `on_dispose`: freeing resources already freed, double-closing connections, or referencing members cleaned up in on_stop that ran earlier in teardown.
Common situations: Shutdown of a live node where dispose closes a socket already closed in on_stop; deleting temp files that no longer exist; accessing a client set to None during teardown.
Related errors
- Python on_stop failed: {e}
- Python on_resume failed: {e}
- Python on_reset failed: {e}
- Python on_degrade failed: {e}
- Python on_fault failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/d7d425524115060b.
Report an issue: GitHub.