{"record":{"id":"16942c9c0555079f","repo":"nautechsystems/nautilus_trader","slug":"python-on-dispose-failed-e-16942c","errorCode":null,"errorMessage":"Python on_dispose failed: {e}","messagePattern":"Python on_dispose failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/trading/src/python/strategy.rs","lineNumber":1081,"sourceCode":"\n    fn on_stop(&mut self) -> anyhow::Result<()> {\n        self.dispatch_on_stop()\n            .map_err(|e| anyhow::anyhow!(\"Python on_stop failed: {e}\"))\n    }\n\n    fn on_resume(&mut self) -> anyhow::Result<()> {\n        self.dispatch_on_resume()\n            .map_err(|e| anyhow::anyhow!(\"Python on_resume failed: {e}\"))\n    }\n\n    fn on_reset(&mut self) -> anyhow::Result<()> {\n        self.dispatch_on_reset()\n            .map_err(|e| anyhow::anyhow!(\"Python on_reset failed: {e}\"))\n    }\n\n    fn on_dispose(&mut self) -> anyhow::Result<()> {\n        self.dispatch_on_dispose()\n            .map_err(|e| anyhow::anyhow!(\"Python on_dispose failed: {e}\"))\n    }\n\n    fn on_degrade(&mut self) -> anyhow::Result<()> {\n        self.dispatch_on_degrade()\n            .map_err(|e| anyhow::anyhow!(\"Python on_degrade failed: {e}\"))\n    }\n\n    fn on_fault(&mut self) -> anyhow::Result<()> {\n        self.dispatch_on_fault()\n            .map_err(|e| anyhow::anyhow!(\"Python on_fault failed: {e}\"))\n    }\n\n    fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {\n        self.dispatch_on_save()\n            .map_err(|e| anyhow::anyhow!(\"Python on_save failed: {e}\"))\n    }\n\n    fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {","sourceCodeStart":1063,"sourceCodeEnd":1099,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/trading/src/python/strategy.rs#L1063-L1099","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (strategy.py)\ndef on_dispose(self):\n    self._file.close()  # raises if file was never opened or already closed\n\n// after\ndef on_dispose(self):\n    if self._file is not None and not self._file.closed:\n        self._file.close()\n    self._file = None","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def safe_close(resource) -> None:\n    if resource is None:\n        return\n    close = getattr(resource, \"close\", None)\n    if callable(close) and not getattr(resource, \"closed\", False):\n        close()","tryCatchPattern":"def on_dispose(self):\n    try:\n        safe_close(getattr(self, \"_file\", None))\n    except Exception as e:\n        self.log.warning(f\"dispose cleanup issue: {e}\")  # never re-raise in dispose","preventionTips":["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."],"tags":["python","strategy","lifecycle","on-dispose"],"backgroundTag":"python-callback-raised","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}