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


##########
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 in 840e939ff1a1525f00c986570e8d6104f12f5f8e. The stop request uses a 
conditional UPDATE excluding SUCCESS, FAILED and TIMED_OUT before any staged 
cancellation metadata can autoflush. If publication won, a locking refresh 
discards the losing stop's pending fields. The worker's stop-payload writer 
uses the same guard and returns the recorded terminal status.
   
   Regression tests added by this PR:
   - `test_stop_query_preserves_concurrently_committed_terminal_result`: a 
second session commits the terminal status, results key, end time and extra 
metadata inside `cancel_query`, after the stop's initial RUNNING read. All six 
cases fail at ea6168068bb2473e25fa39e4c9ccaaab143039fa and pass at 
840e939ff1a1525f00c986570e8d6104f12f5f8e.
   - `test_stopped_payload_preserves_concurrently_committed_terminal_result`: 
all three worker-payload sibling cases fail before and pass after.
   - `test_stop_query_commits_stop_with_early_cancel`: all five cases fail 
before and pass after, checking that an incidental metadata read cannot flush 
the early-cancel flag separately from STOPPED.
   
   Evidence: `PYTHONPATH=/tmp/r456-deps:./superset-core/src:. pytest -q 
tests/unit_tests/daos/test_query_dao.py 
tests/unit_tests/sql/execution/test_utils.py -m 'not manual and not 
llm_compliance'` gives `18 failed` in a separate checkout of 
ea6168068bb2473e25fa39e4c9ccaaab143039fa and `18 passed` at 
840e939ff1a1525f00c986570e8d6104f12f5f8e. The broader SQL/DAO/legacy suites 
give `2648 passed, 2 skipped, 1 deselected`; the additional base/SQLite/Trino 
engine-spec suite gives `350 passed`. `pre-commit run --files <all PR-changed 
paths>` and `pre-commit run mypy` pass. The concurrency tests exercise SQLite; 
Postgres/MySQL runtime checks were not run.
   
   Siblings checked:
   - superset/daos/query.py:84 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/celery_task.py:480 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql_lab.py:154 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:161 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:433 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:529 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:647 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:674 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:710 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:712 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:753 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:862 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:914 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/sql_lab.py:923 — not affected: status comparison or payload only; 
no STOPPED metadata write
   - superset/db_engine_specs/presto.py:1433 — not affected: reads stop intent 
to cancel the engine cursor; does not assign STOPPED
   - superset/sql/execution/utils.py:125 — not affected: status comparison or 
payload only; no STOPPED metadata write
   - superset/sql/execution/utils.py:152 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/utils.py:161 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/db_engine_specs/trino.py:375 — not affected: reads stop intent to 
cancel the engine cursor; does not assign STOPPED
   - superset/sql/execution/celery_task.py:469 — not affected: conditional 
worker terminal write excludes STOPPED; does not write STOPPED
   - superset/sql/execution/celery_task.py:499 — not affected: status 
comparison or payload only; no STOPPED metadata write
   - superset/db_engine_specs/impala.py:199 — not affected: reads stop intent 
to cancel the engine cursor; does not assign STOPPED
   - superset/db_engine_specs/hive.py:447 — not affected: reads stop intent to 
cancel the engine cursor; does not assign STOPPED
   - superset/sql/execution/executor.py:1339 — not affected: maps a stored 
status into the public status enum; no metadata write
   - superset/common/db_query_status.py:23 — not affected: enum definition only
   - superset/charts/schemas.py:1865 — not affected: schema validation choice 
only
   - superset/utils/core.py:422 — not affected: enum definition only
   - superset/mcp_service/chart/query_result.py:55 — not affected: 
result-status classification only
   
   Searches: `rg -n 'QueryStatus.STOPPED|status\s*=\s*["\x27]stopped' 
superset/` and `rg -n "[\'\"]stopped[\'\"]" superset/ --glob "*.py"`.
   



##########
superset/sql_lab.py:
##########
@@ -62,6 +63,7 @@
 from superset.extensions import celery_app, event_logger
 from superset.models.sql_lab import Query
 from superset.result_set import SupersetResultSet
+from superset.sql.execution.celery_task import stop_seen_during_execution

Review Comment:
   Addressed in 840e939ff1a1525f00c986570e8d6104f12f5f8e. Both paths import 
`_extract_errors`, `_discard_unreferenced_results`, the delete helper, and 
`stop_seen_during_execution` from `superset/sql/execution/utils.py`; the legacy 
path does not import the Celery task module. The cleanup rule is unchanged, 
with the caller's results backend passed explicitly.
   
   `test_execution_helpers_are_shared` checks all four helpers are the 
identical function objects from the neutral module (including the legacy 
delete-helper alias). All four cases fail at 
ea6168068bb2473e25fa39e4c9ccaaab143039fa and pass at 
840e939ff1a1525f00c986570e8d6104f12f5f8e.
   
   Evidence: `PYTHONPATH=/tmp/r456-deps:./superset-core/src:. pytest -q 
tests/unit_tests/daos/test_query_dao.py 
tests/unit_tests/sql/execution/test_utils.py -m 'not manual and not 
llm_compliance'` gives `18 failed` before / `18 passed` after. Existing error 
extraction, cleanup, referenced-blob preservation, and stop-observation tests 
remain green in the SQL/DAO/legacy suites: `2648 passed, 2 skipped, 1 
deselected`. Additional engine-spec tests: `350 passed`. `pre-commit run 
--files <all PR-changed paths>` and `pre-commit run mypy` pass.
   
   Siblings checked:
   - superset/sql_lab.py:66 — fixed in 840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/celery_task.py:54 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/utils.py:40 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/utils.py:51 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/utils.py:81 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/utils.py:114 — fixed in 
840e939ff1a1525f00c986570e8d6104f12f5f8e
   - superset/sql/execution/executor.py:1230 — not affected: imports the Celery 
task to queue asynchronous work, not to reuse these helpers
   
   `rg -n '^def 
(_extract_errors|_discard_stored_results|_discard_unpublished_results|_discard_unreferenced_results|stop_seen_during_execution)'
 superset/ --glob '*.py'` finds only the four neutral definitions.
   



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