From 57b0fd10efd6f0aa9cff82bc44b7baf32df2d129 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Wed, 11 Feb 2026 09:53:56 +0100 Subject: [PATCH 1/2] fix: handle TimeoutError in Actor __aexit__ to prevent resource leaks When Actor cleanup exceeded `_cleanup_timeout`, the unhandled `TimeoutError` from `asyncio.wait_for` left the Actor in a broken state: `_is_initialized` was never reset, event/charging managers were left partially shut down, and `sys.exit()` was never called. Now the timeout is caught and critical resource cleanup (event manager, charging manager) is attempted even after timeout. `_is_initialized` is reset in a `finally` block to guarantee consistent state. Co-Authored-By: Claude Opus 4.6 --- src/apify/_actor.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/apify/_actor.py b/src/apify/_actor.py index 4fd2989e..b26b392a 100644 --- a/src/apify/_actor.py +++ b/src/apify/_actor.py @@ -252,8 +252,17 @@ async def finalize() -> None: # Persist Actor state await self._save_actor_state() - await asyncio.wait_for(finalize(), self._cleanup_timeout.total_seconds()) - self._is_initialized = False + try: + await asyncio.wait_for(finalize(), self._cleanup_timeout.total_seconds()) + except TimeoutError: + self.log.warning('Actor cleanup timed out, forcing shutdown of event manager and charging manager') + # Ensure critical resources are cleaned up even after timeout + with suppress(Exception): + await self.event_manager.__aexit__(None, None, None) + with suppress(Exception): + await self._charging_manager_implementation.__aexit__(None, None, None) + finally: + self._is_initialized = False if self._exit_process: sys.exit(self.exit_code) From ce4dfcfedd61b926bb2bc6fc2a5377a415f46979 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Wed, 11 Feb 2026 10:43:14 +0100 Subject: [PATCH 2/2] Simplify TimeoutError handler: just log, don't attempt forced cleanup Removed the forced __aexit__ calls on event_manager and charging_manager after timeout - if finalize() timed out, those calls would likely hang too. Co-Authored-By: Claude Opus 4.6 --- src/apify/_actor.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/apify/_actor.py b/src/apify/_actor.py index b26b392a..76f4475b 100644 --- a/src/apify/_actor.py +++ b/src/apify/_actor.py @@ -255,12 +255,7 @@ async def finalize() -> None: try: await asyncio.wait_for(finalize(), self._cleanup_timeout.total_seconds()) except TimeoutError: - self.log.warning('Actor cleanup timed out, forcing shutdown of event manager and charging manager') - # Ensure critical resources are cleaned up even after timeout - with suppress(Exception): - await self.event_manager.__aexit__(None, None, None) - with suppress(Exception): - await self._charging_manager_implementation.__aexit__(None, None, None) + self.log.exception('Actor cleanup timed out') finally: self._is_initialized = False