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


##########
superset/sql/execution/celery_task.py:
##########
@@ -360,27 +431,203 @@ def execute_sql_task(
                         query_id,
                         rendered_query,
                         start_time=start_time,
+                        on_commit=mark_committed,
+                        on_results_key=track_results_key,
                     ),
                     can_retry=lambda: not query.progress,
                 )
             except Exception as ex:
                 logger.exception("Query %d: %s", query_id, ex)
                 stats_logger = app.config["STATS_LOGGER"]
                 stats_logger.incr("error_sqllab_unhandled")
-                query = _get_query(query_id=query_id)
-                return _handle_query_error(ex, query)
+                try:
+                    query = _get_query_after_error(query_id)
+                    # Failures after the target commit (result serialization,
+                    # the results backend, closing the connection) cannot undo
+                    # it.
+                    return _handle_query_error(
+                        ex, query, durable=committed, results_key=results_key
+                    )
+                except Exception:
+                    # _handle_query_error commits last, so the failure was most
+                    # likely not recorded. A commit can still apply and raise.
+                    _discard_unreferenced_results(query_id, results_key)
+                    raise
+
+
+def _get_query_after_error(query_id: int) -> Query:
+    """Get the query, rolling back a session a failed flush left unusable."""
+    try:
+        return _get_query(query_id=query_id)
+    except Exception:  # pylint: disable=broad-except
+        # Rolling back only discards metadata the failed task never committed.
+        db.session.rollback()  # pylint: disable=consider-using-transaction
+        return _get_query(query_id=query_id)
+
+
+def _commit_query_unless_stopped(query: Query) -> bool:
+    """Atomically guard terminal metadata writes against a concurrent stop."""
+    with db.session.no_autoflush:
+        # The conditional write acquires the row's write lock until commit.
+        # A separate status read cannot protect the subsequent ORM flush.
+        updated = (
+            db.session.query(Query)
+            .filter(Query.id == query.id, Query.status != QueryStatus.STOPPED)

Review Comment:
   Fixed the remaining Trino/Impala stale-metadata overwrite in 
`3d14338e31b03a4b5ca191dfa7f21487a510eb2b`: engine preparation only stages 
intent, and both the DAO and async-handle callers persist it through the shared 
conditional stop guard. A losing stop discards its staged metadata instead of 
flushing it over a terminal result.
   Regression tests added here: 
`tests/unit_tests/daos/test_query_dao.py::test_engine_early_cancel_preserves_concurrently_committed_terminal_metadata`
 and `test_engine_early_cancel_commits_with_guarded_stop`. `python3 -m pytest 
-q tests/unit_tests/daos/test_query_dao.py -k test_engine_early_cancel -m 'not 
manual and not llm_compliance' --tb=short` → 16 failed before at 
`840e939ff1a1525f00c986570e8d6104f12f5f8e`, 16 passed after. These exercise the 
real Trino/Impala preparation paths; a second session commits 
SUCCESS/FAILED/TIMED_OUT with columns/errors/results before the stale stop 
proceeds.
   `python3 -m pytest -q tests/unit_tests/sql/ tests/unit_tests/daos/ 
tests/unit_tests/db_engine_specs/test_trino.py 
tests/unit_tests/db_engine_specs/test_impala.py -m 'not manual and not 
llm_compliance' --tb=short` → 2704 passed, 2 skipped. Staged-file `pre-commit 
run` and `pre-commit run --files` covering every file changed by the branch 
both passed, including mypy. Concurrency coverage is SQLite only; 
PostgreSQL/MySQL concurrency was not run.
   Siblings checked: superset/db_engine_specs/trino.py:504 — fixed in 
3d14338e31b03a4b5ca191dfa7f21487a510eb2b; 
superset/db_engine_specs/impala.py:237 — fixed in 
3d14338e31b03a4b5ca191dfa7f21487a510eb2b; 
superset/sql/execution/executor.py:1432 — fixed in 
3d14338e31b03a4b5ca191dfa7f21487a510eb2b; superset/daos/query.py:65 — not 
affected: cancellation and the shared guard already run under no_autoflush in 
one transaction; superset/sql_lab.py:971 — not affected: stages early intent 
without committing and delegates to the audited engine methods; 
superset/sql/execution/executor.py:1449 — not affected: stages intent without 
committing and its async caller owns the guarded transaction; 
superset/tasks/query_cancel.py:151 — not affected: passes a nonpersistent 
cancellation stub and does not write a Query row; 
superset/db_engine_specs/base.py:3018 — not affected: default preparation is a 
no-op; superset/db_engine_specs/base.py:3117 — not affected: default 
cancellation returns
  False without writes; superset/db_engine_specs/mysql.py:723 — not affected: 
warehouse cancellation only, no Query/session writes; 
superset/db_engine_specs/postgres.py:1053 — not affected: warehouse 
cancellation only, no Query/session writes; 
superset/db_engine_specs/redshift.py:394 — not affected: warehouse cancellation 
only, no Query/session writes; superset/db_engine_specs/snowflake.py:545 — not 
affected: warehouse cancellation only, no Query/session writes; 
superset/db_engine_specs/singlestore.py:571 — not affected: warehouse 
cancellation only, no Query/session writes; 
superset/db_engine_specs/oracle.py:169 — not affected: warehouse cancellation 
only, no Query/session writes; superset/db_engine_specs/ocient.py:392 — not 
affected: warehouse cancellation plus in-memory mapping removal, no 
metadata-session writes; superset/db_engine_specs/impala.py:151 — not affected: 
worker cursor cancellation has no metadata writes; 
superset/db_engine_specs/impala.py:220 — not a
 ffected: worker progress commit, not called by the stop request; 
superset/db_engine_specs/trino.py:345 — not affected: worker handle/progress 
metadata writes at 345–404, not called by the stop request; 
superset/db_engine_specs/base.py:2032 — not affected: worker cancel-ID 
publication, not called by the stop request; 
superset/db_engine_specs/base.py:886 — not affected: OAuth setup commit, not a 
cancellation-metadata write; superset/db_engine_specs/hive.py:488 — not 
affected: worker progress commit and implicit cancellation, no stop-request 
Query commit; superset/db_engine_specs/presto.py:1416 — not affected: worker 
tracking/progress commits at 1416/1455 and implicit cancellation, not 
stop-request writes; superset/db_engine_specs/datastore.py:565 — not affected: 
stages worker warnings without committing, outside cancellation; 
superset/db_engine_specs/gsheets.py:702 — not affected: spreadsheet catalog 
writes a Database, not a Query.
   
   Search: `rg -n 'db.session.(commit|flush)|set_extra_json_key|extra_json|def 
.*cancel' superset/db_engine_specs/*.py`, plus every 
`cancel_query`/`prepare_cancel_query` call site under `superset/` and AST 
inspection of all engine cancellation methods.
   Merge coordination: `git merge-tree --write-tree` against 
`758e87f60fa44f24e8d7297aa61a4cfb3d4bf28c` conflicts in either order, including 
`impala.py`. I recommend landing this PR first, then reconciling 
https://github.com/apache/superset/pull/45232 while retaining stage-only engine 
preparation and the shared caller guard. This change to `impala.py` is just the 
removal of its early commit.



-- 
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