From 122f101c87e78b7d41d4036fb074ca3a0b0c8028 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Wed, 22 Apr 2026 09:44:29 +0200 Subject: [PATCH] fix: correct deadline logic in _wait_for_finish Make the not-found deadline lazy (starts on the first observed 404 and resets after any successful response) and honor the user-provided wait_duration in the 404 branch. This prevents a single transient 404 from aborting a long poll early, and prevents overruns when the user's deadline is shorter than DEFAULT_WAIT_WHEN_JOB_NOT_EXIST. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../_resource_clients/_resource_client.py | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/apify_client/_resource_clients/_resource_client.py b/src/apify_client/_resource_clients/_resource_client.py index 258c743b..a4451c7c 100644 --- a/src/apify_client/_resource_clients/_resource_client.py +++ b/src/apify_client/_resource_clients/_resource_client.py @@ -294,9 +294,8 @@ def _wait_for_finish( Raises: ApifyApiError: If API returns errors other than 404. """ - now = datetime.now(UTC) - deadline = (now + wait_duration) if wait_duration is not None else None - not_found_deadline = now + DEFAULT_WAIT_WHEN_JOB_NOT_EXIST + deadline = (datetime.now(UTC) + wait_duration) if wait_duration is not None else None + not_found_deadline: datetime | None = None actor_job: dict = {} while True: @@ -317,6 +316,9 @@ def _wait_for_finish( actor_job_response = ActorJobResponse.model_validate(result) actor_job = actor_job_response.data.model_dump() + # Reset the not-found streak so a later transient 404 gets its own grace window. + not_found_deadline = None + is_terminal = actor_job_response.data.status in TERMINAL_STATUSES is_timed_out = deadline is not None and datetime.now(UTC) >= deadline @@ -326,9 +328,12 @@ def _wait_for_finish( except ApifyApiError as exc: catch_not_found_or_throw(exc) - # If there are still not found errors after DEFAULT_WAIT_WHEN_JOB_NOT_EXIST, we give up - # and return None. In such case, the requested record probably really doesn't exist. - if datetime.now(UTC) > not_found_deadline: + now = datetime.now(UTC) + if deadline is not None and now >= deadline: + return None + if not_found_deadline is None: + not_found_deadline = now + DEFAULT_WAIT_WHEN_JOB_NOT_EXIST + elif now > not_found_deadline: return None # It might take some time for database replicas to get up-to-date so sleep a bit before retrying @@ -474,9 +479,8 @@ async def _wait_for_finish( Raises: ApifyApiError: If API returns errors other than 404. """ - now = datetime.now(UTC) - deadline = (now + wait_duration) if wait_duration is not None else None - not_found_deadline = now + DEFAULT_WAIT_WHEN_JOB_NOT_EXIST + deadline = (datetime.now(UTC) + wait_duration) if wait_duration is not None else None + not_found_deadline: datetime | None = None actor_job: dict = {} while True: @@ -497,6 +501,9 @@ async def _wait_for_finish( actor_job_response = ActorJobResponse.model_validate(result) actor_job = actor_job_response.data.model_dump() + # Reset the not-found streak so a later transient 404 gets its own grace window. + not_found_deadline = None + is_terminal = actor_job_response.data.status in TERMINAL_STATUSES is_timed_out = deadline is not None and datetime.now(UTC) >= deadline @@ -506,9 +513,12 @@ async def _wait_for_finish( except ApifyApiError as exc: catch_not_found_or_throw(exc) - # If there are still not found errors after DEFAULT_WAIT_WHEN_JOB_NOT_EXIST, we give up - # and return None. In such case, the requested record probably really doesn't exist. - if datetime.now(UTC) > not_found_deadline: + now = datetime.now(UTC) + if deadline is not None and now >= deadline: + return None + if not_found_deadline is None: + not_found_deadline = now + DEFAULT_WAIT_WHEN_JOB_NOT_EXIST + elif now > not_found_deadline: return None # It might take some time for database replicas to get up-to-date so sleep a bit before retrying