This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 3bd4df6b2da Fix Snowflake SQL API wait_for_query timing out on 
finished queries (#73761)
3bd4df6b2da is described below

commit 3bd4df6b2da90320412c486ccc2ad861b33b9b8a
Author: Karthik Mohankumar <[email protected]>
AuthorDate: Sun Oct 4 19:22:12 2026 -0500

    Fix Snowflake SQL API wait_for_query timing out on finished queries (#73761)
    
    * Fix Snowflake SQL API wait_for_query timing out on finished queries
    
    wait_for_query checked the elapsed time before looking at the status it had 
just fetched, so a status call that returned after the timeout raised 
TimeoutError even when the query had already succeeded or failed. Status calls 
can take several seconds because they retry with exponential backoff, and the 
OpenLineage integration waits with a 3-second timeout, so a finished query 
could be reported as timed out and its lineage details dropped. Elapsed time 
now also uses the monotonic clock  [...]
    
    * Test wait_for_query with a status call that outlasts the timeout
---
 .../providers/snowflake/hooks/snowflake_sql_api.py | 12 ++++----
 .../unit/snowflake/hooks/test_snowflake_sql_api.py | 35 +++++++++++++++++++---
 2 files changed, 37 insertions(+), 10 deletions(-)

diff --git 
a/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake_sql_api.py
 
b/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake_sql_api.py
index 05d80a39e48..4dca2b0a12a 100644
--- 
a/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake_sql_api.py
+++ 
b/providers/snowflake/src/airflow/providers/snowflake/hooks/snowflake_sql_api.py
@@ -383,21 +383,21 @@ class SnowflakeSqlApiHook(SnowflakeHook):
         :raises RuntimeError: If the query status is 'error' and `raise_error` 
is True.
         :raises TimeoutError: If the query doesn't finish within the specified 
timeout.
         """
-        start_time = time.time()
+        start_time = time.monotonic()
 
         while True:
             response = self.get_sql_api_query_status(query_id=query_id)
             self.log.debug("Query status `%s`", response["status"])
 
-            if time.time() - start_time > timeout:
-                raise TimeoutError(
-                    f"Query `{query_id}` did not finish within the timeout 
period of {timeout} seconds."
-                )
-
             if response["status"] != "running":
                 self.log.info("Query status `%s`", response["status"])
                 break
 
+            if time.monotonic() - start_time > timeout:
+                raise TimeoutError(
+                    f"Query `{query_id}` did not finish within the timeout 
period of {timeout} seconds."
+                )
+
             time.sleep(poll_interval)
 
         if response["status"] == "error" and raise_error:
diff --git 
a/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake_sql_api.py 
b/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake_sql_api.py
index eb4fc930c6e..85f93a87ad8 100644
--- a/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake_sql_api.py
+++ b/providers/snowflake/tests/unit/snowflake/hooks/test_snowflake_sql_api.py
@@ -1249,8 +1249,9 @@ class TestSnowflakeSqlApiHook:
         assert result == {"status": "queued", "info": ["a", "b"]}
         sleep_mock.assert_not_called()
 
+    @mock.patch(f"{MODULE_PATH}.time.monotonic")
     @mock.patch(f"{MODULE_PATH}.time.sleep")
-    def test_wait_for_query_timeout_error(self, sleep_mock, time_machine):
+    def test_wait_for_query_timeout_error(self, sleep_mock, monotonic_mock):
         hook = SnowflakeSqlApiHook(snowflake_conn_id="test_conn")
 
         # Simulate a query that keeps running and never finishes
@@ -1259,9 +1260,13 @@ class TestSnowflakeSqlApiHook:
         qid = "qid-789"
         timeout = 3
 
-        # Freeze the clock. Each sleep advances it explicitly so logger 
time.time() calls do not skew the timeout.
-        time_machine.move_to(0, tick=False)
-        sleep_mock.side_effect = lambda seconds: time_machine.shift(seconds + 
0.1)
+        clock = [0.0]
+        monotonic_mock.side_effect = lambda: clock[0]
+
+        def advance_clock(seconds):
+            clock[0] += seconds + 0.1
+
+        sleep_mock.side_effect = advance_clock
 
         with pytest.raises(TimeoutError):
             hook.wait_for_query(query_id=qid, timeout=timeout, poll_interval=1)
@@ -1272,6 +1277,28 @@ class TestSnowflakeSqlApiHook:
         assert hook.get_sql_api_query_status.call_count == 4
         
hook.get_sql_api_query_status.assert_has_calls([mock.call(query_id=qid)] * 4)
 
+    @pytest.mark.parametrize("final_status", ["success", "error"])
+    @mock.patch(f"{MODULE_PATH}.time.monotonic")
+    @mock.patch(f"{MODULE_PATH}.time.sleep")
+    def test_wait_for_query_returns_finished_status_after_timeout_elapsed(
+        self, sleep_mock, monotonic_mock, final_status
+    ):
+        """A status check that outlasts the timeout still reports a query that 
has finished."""
+        hook = SnowflakeSqlApiHook(snowflake_conn_id="test_conn")
+        clock = [0.0]
+        monotonic_mock.side_effect = lambda: clock[0]
+
+        def finish_after_slow_status_call(query_id):
+            clock[0] += 10
+            return {"status": final_status, "message": "done"}
+
+        hook.get_sql_api_query_status = 
mock.MagicMock(side_effect=finish_after_slow_status_call)
+
+        result = hook.wait_for_query(query_id="qid-slow", timeout=3, 
poll_interval=1)
+
+        assert result == {"status": final_status, "message": "done"}
+        sleep_mock.assert_not_called()
+
     @mock.patch(f"{HOOK_PATH}._make_api_call_with_retries")
     @mock.patch(f"{HOOK_PATH}._process_response")
     @mock.patch(f"{HOOK_PATH}.get_request_url_header_params")

Reply via email to