aminghadersohi commented on code in PR #45845:
URL: https://github.com/apache/superset/pull/45845#discussion_r4430548326


##########
superset/db_engine_specs/impala.py:
##########
@@ -136,30 +139,43 @@ def execute(
     @classmethod
     def fetch_data(cls, cursor: Any, limit: int | None = None) -> 
list[tuple[Any, ...]]:
         """Wait for asynchronous operations using the public cursor API."""
-        if callable(getattr(cursor, "execute_async", None)):
-            from impala.error import Error
-
-            deadline = time.monotonic() + app.config["SQLLAB_TIMEOUT"]
+        try:
+            if callable(getattr(cursor, "execute_async", None)):
+                from impala.error import Error
+
+                deadline = time.monotonic() + app.config["SQLLAB_TIMEOUT"]
+                try:
+                    while cursor.is_executing():
+                        if time.monotonic() >= deadline:
+                            cls._cancel_operation(cursor)
+                            raise TimeoutError(
+                                "Timed out waiting for the Impala operation"
+                            )
+                        time.sleep(0.1)
+                    if cursor.execution_failed():
+                        # Fetching surfaces the driver's detailed asynchronous 
error,
+                        # including errors on statements that produce no 
result set.
+                        cursor.fetchall()
+                except Error as ex:
+                    raise cls.get_dbapi_mapped_exception(ex) from ex
+            return super().fetch_data(cursor, limit)
+        except TIME_LIMIT_EXCEPTIONS:
+            # The operation is released before the time limit propagates so
+            # it does not keep running on the coordinator.
             try:
-                while cursor.is_executing():
-                    if time.monotonic() >= deadline:
-                        cursor.cancel_operation()
-                        raise TimeoutError("Timed out waiting for the Impala 
operation")
-                    time.sleep(0.1)
-                if cursor.execution_failed():
-                    # Fetching surfaces the driver's detailed asynchronous 
error,
-                    # including errors on statements that produce no result 
set.
-                    cursor.fetchall()
-            except Error as ex:
-                raise cls.get_dbapi_mapped_exception(ex) from ex
-        return super().fetch_data(cursor, limit)
+                cursor.cancel_operation()
+            except TIME_LIMIT_EXCEPTIONS:
+                raise
+            except Exception:  # pylint: disable=broad-except
+                logger.warning("cancel_operation() failed after a time limit")
+            raise

Review Comment:
   Follow-up to my reply above: it said no cancel RPC is sent when the 
transport reset is skipped, which was true for the cancel but not for cleanup. 
On that path impyla's `cursor.close()` (the executor reaches it through 
`closing(conn.cursor())`) and the cursor finalizer still sent CloseOperation 
and CloseSession, reopening the transport with the interrupted reply still 
buffered.
   
   Fixed in 365c5e877d6cb893da2e2b590bed770a4c2c4c41: 
`_cancel_operation_after_time_limit` marks the cursor closed 
(`_abandon_cursor`) when the reset returns False or raises, and when a second 
time limit lands during the reset, so impyla's `close()` and finalizer send 
nothing.
   
   Regression test: 
`tests/unit_tests/db_engine_specs/test_impala_transport.py::test_closing_the_cursor_sends_nothing_after_a_skipped_cancel`
 [binary, sasl, http] x [unknown_transport, reset_raises]. It runs 
`handle_cursor` inside `closing(cursor)` over the real impyla transports, 
interrupts the first status reply, then calls the finalizer, and asserts the 
server received no request after the interrupted one. Without `_abandon_cursor` 
all 6 fail (`TTransportException: read timeout` on binary and SASL, `EOFError` 
over HTTP, and that error replaces the time-limit exception); with it they 
pass. Mock tests: `test_time_limit_skips_cancel_rpcs_without_a_clean_transport` 
now also asserts the cursor is marked closed, and 
`test_time_limit_during_transport_reset_propagates` covers a second 
SoftTimeLimitExceeded or SupersetTimeoutException during the reset. 
test_impala, test_impala_pending, test_impala_transport, test_hive, 
test_presto, test_trino: 473 passed.
   
   Siblings checked:
   - superset/db_engine_specs/impala.py:180 — fixed in 
365c5e877d6cb893da2e2b590bed770a4c2c4c41 (second time limit during the reset)
   - superset/db_engine_specs/impala.py:190 — fixed in 
365c5e877d6cb893da2e2b590bed770a4c2c4c41 (reset returned False or raised)
   - superset/sql/execution/executor.py:920 — not affected: closes the cursor 
through `closing()`, which is now a no-op for an abandoned cursor
   - superset/sql/execution/executor.py:1647 — not affected: same `closing()` 
pattern, a no-op for an abandoned cursor
   - superset/sql_lab.py:593 — not affected: the cursor is not closed 
explicitly; its finalizer is a no-op for an abandoned cursor and the connection 
close only closes the transport
   
   Remaining: `requirements/development.txt` does not install impyla or thrift, 
so CI skips `test_impala_transport.py` through `pytest.importorskip`; I ran it 
locally with impyla 0.24.0, thrift 0.24.0 and thrift_sasl 0.4.3. The items 
listed under Remaining in my reply above still apply.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to