032: deep async propagation — orchestrator, insert, mapper, batch chains

Full async conversion for all sync callers of async SupersetClient methods:

orchestrator_sql.py: generate_and_insert_sql + _resolve_dialect → async
orchestrator_run_completion.py: complete_success → async (calls generate_and_insert_sql)
orchestrator_exec.py: execute_run → async (awaits complete_success)
orchestrator_runner.py: execute_run → async (delegates to engine)
orchestrator.py: execute_run + _generate_and_insert_sql → async
executor.py: _insert_batch_to_target → async (awaits batch insert)
_batch_proc.py: insert_batch_to_target → async
_batch_insert.py: insert_batch_to_target + _resolve_insert_backend + _execute_insert_sql → async
dataset_mapper.py: get_sqllab_mappings + run_mapping → async
  + await get_dataset, update_dataset, execute_and_poll
mapper.py: await on run_mapping + resolve_database_id calls
_run_routes.py: threading.Thread → asyncio.create_task (_background_execute async)
This commit is contained in:
2026-06-05 00:13:01 +03:00
parent f416583a8c
commit ee9123bcf2
11 changed files with 51 additions and 94 deletions

View File

@@ -7,6 +7,7 @@
from datetime import UTC, datetime
import asyncio
from fastapi import Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
@@ -47,16 +48,12 @@ def run_translation(
orch = TranslationOrchestrator(db, config_manager, current_user.username)
run = orch.start_run(job_id=job_id, is_scheduled=False, full_translation=full_translation)
# The request-scoped db session will be closed after this handler returns.
# The background thread must use its OWN session to avoid operating on a
# closed or expunged session.
import threading
# Capture request trace_id for background thread correlation
# The background task must use its OWN session.
_request_trace_id = get_trace_id()
def _background_execute():
async def _background_execute():
from ....models.translate import TranslationRun as TRModel
# Preserve request's trace_id for log correlation across the thread boundary
# Preserve request's trace_id for log correlation
if _request_trace_id:
set_trace_id(_request_trace_id)
else:
@@ -68,8 +65,6 @@ def run_translation(
bg_db, config_manager,
current_user.username if current_user else None,
)
# Re-fetch the run within the background session to get a fresh,
# attached object
bg_run = bg_db.query(TRModel).filter(TRModel.id == run.id).first()
if bg_run is None:
logger.explore(
@@ -78,55 +73,30 @@ def run_translation(
)
return
# execute_run internally calls self.db.commit()
bg_orch.execute_run(bg_run)
await bg_orch.execute_run(bg_run)
# ----- VERIFICATION -----
# After execute_run the run object from bg_db is detached after
# commit. Re-query to verify the status was persisted in the
# database and is a terminal state.
check_run = bg_db.query(TRModel).filter(TRModel.id == run.id).first()
if check_run and check_run.status in ("COMPLETED", "FAILED", "CANCELLED"):
logger.reason(
"Background execute verified",
extra={
"src": "translate_routes",
"run_id": run.id,
"status": check_run.status,
},
extra={"src": "translate_routes", "run_id": run.id, "status": check_run.status},
)
else:
# execute_run appeared to succeed but the run is still in a
# non-terminal state — manually fail it so the frontend
# polling can detect the terminal state and stop spinning.
actual_status = check_run.status if check_run else "NOT_FOUND"
logger.explore(
"Background execute: run not in terminal state after commit",
extra={
"src": "translate_routes",
"run_id": run.id,
"status": actual_status,
},
extra={"src": "translate_routes", "run_id": run.id, "status": actual_status},
)
if check_run:
check_run.status = "FAILED"
check_run.error_message = (
"Background execution did not reach terminal state "
f"(was: {actual_status})"
)
check_run.error_message = f"Background execution did not reach terminal state (was: {actual_status})"
bg_db.commit()
except Exception as bg_err:
logger.explore(
"Background execute failed",
extra={
"src": "translate_routes",
"run_id": run.id,
"error": str(bg_err),
},
extra={"src": "translate_routes", "run_id": run.id, "error": str(bg_err)},
)
# Mark the run as FAILED so the frontend polling can detect a
# terminal state and stop spinning.
try:
fb_db = SessionLocal()
try:
@@ -136,29 +106,18 @@ def run_translation(
fb_run.error_message = f"Background execute error: {bg_err}"
fb_run.completed_at = datetime.now(UTC)
fb_db.commit()
logger.reason(
"Background execute: run marked FAILED",
extra={"src": "translate_routes", "run_id": run.id},
)
logger.reason("Background execute: run marked FAILED",
extra={"src": "translate_routes", "run_id": run.id})
finally:
fb_db.close()
except Exception as fb_err:
logger.explore(
"Background execute: unable to mark run FAILED",
extra={
"src": "translate_routes",
"run_id": run.id,
"error": str(fb_err),
},
)
logger.explore("Background execute: unable to mark run FAILED",
extra={"src": "translate_routes", "run_id": run.id, "error": str(fb_err)})
finally:
if bg_db is not None:
bg_db.close()
threading.Thread(
target=_background_execute,
daemon=True,
).start()
asyncio.create_task(_background_execute())
return _run_to_response(run)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))