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:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user