sjyangkevin opened a new pull request, #74512:
URL: https://github.com/apache/airflow/pull/74512

   ### Why
   
   closes: #67643
   
   When a task's `executor_config` can't be converted to a pod (for example the 
legacy `{"KubernetesExecutor": {...}}` dict), `KubernetesExecutor` reports the 
task as failed, but the scheduler ignores that event. The task instance stays 
`queued` until `[scheduler] task_queued_timeout` cleans it up, and one executor 
parallelism slot leaks for the life of the scheduler process. With this change 
the task is marked failed right away.
   
   ### Root cause
   
   `running` means "this executor owns the task and hasn't reported its final 
state yet". Every exit path removes the key from `running` at the same time it 
writes the final state to `event_buffer` (`fail()` / `success()` / 
`change_state()` / `_change_state()`). `_process_workloads()` broke that rule 
by recording ownership after dispatch:
   
   ```python
   self.execute_async(...)   # invalid config -> self.fail(key): removes 
nothing, buffers FAILED
   self.running.add(key)     # re-adds a key that has already been reported as 
failed
   ```
   
   When the scheduler processes the FAILED event, `executor.has_task(ti)` 
returns `True` because the key is in `running`. The scheduler then treats the 
event as "task requeued" (`scheduler_job_runner.py`, `ti_requeued`) and skips 
failure handling. Nothing ever removes the key, because no pod was created, so 
there's no watcher event.
   
   ### Fix
   
   Add the key to `running` before calling `execute_async()`. A `fail()` inside 
`execute_async()` then removes it through the existing `change_state()` path. 
Pod-creation failures in `sync()` already work this way: they call `fail()` 
while the key is in
   `running`.
   
   - The success path is unchanged. `_process_workloads()` runs synchronously 
in the scheduler thread, and after it returns `running` holds the same keys as 
before. The existing `test_process_workloads` passes unchanged.
   - Any current or future `fail(); return` inside `execute_async()` is covered 
without further changes.
   - Trade-off: if `execute_async()` raised an unexpected exception (not a 
handled `fail()`), the key would now be in `running` while the exception 
propagates. That exception already aborts the scheduler loop, and executor 
state is rebuilt through adoption on restart, so this doesn't make things worse.
   
   ### Tests
   
   - New regression test
     
`test_process_workloads_invalid_executor_config_is_not_tracked_as_running`: it 
runs an
     `ExecuteTask` with the legacy config through `_process_workloads()`, using 
the real
     `execute_async()`. It asserts that the FAILED event is buffered and that 
the key is not
     in `running`. It fails on `main` and passes with the fix. The existing
     `test_invalid_executor_config` calls `execute_async()` directly, which is 
why it never
     caught this.
   
   Results:
   
   - `providers/cncf/kubernetes/tests/unit/cncf/kubernetes/executors/`: 266 
passed, 14 skipped
   - `prek run --stage pre-commit --files <changed files>`: all hooks passed
   - `breeze k8s tests -- -k test_kubernetes_executor` on kind 
(KubernetesExecutor, fixed
     image): 9 passed
   
   ### Reproduction on kind (KubernetesExecutor)
   
   Dag from the issue:
   
   ```python
   with DAG("test_k8s_executor_config_legacy", schedule=None, catchup=False):
       PythonOperator(
           task_id="check_for_failure",
           python_callable=_noop,
           executor_config={"KubernetesExecutor": {"config_file": 
"/some/path/kubeconfig.yaml"}},
       )
   ```
   
   **Before (main):** the task is reported failed but stays queued, and the key 
leaks in `running`:
   
   ```
   04:03:15.497 [error] Invalid executor_config for 01a11ed4-... 
Executor_config: {'KubernetesExecutor': {...}}
   04:03:15.498 [info ] Received executor event with state failed for task 
instance 01a11ed4-...
   04:03:15.513 [info ] TaskInstance Finished: ... state=queued, 
executor_state=failed
   04:03:39.646 [debug] self.running: 
{TaskInstanceUuid(id=UUID('01a11ed4-...'))}
   ```
   
   No worker pod was created, there was no "state mismatch" handling, and the 
task was
   still `queued` more than 10 minutes later.
   
   **After (this PR):** the scheduler reconciles the event and fails the task 
immediately:
   
   ```
   04:33:15.129 [error] Invalid executor_config for 01a11eef-... 
Executor_config: {'KubernetesExecutor': {...}}
   04:33:15.189 [info ] Received executor event with state failed for task 
instance 01a11eef-...
   04:33:15.199 [error] Executor KubernetesExecutor(parallelism=32) reported 
that the task instance ... finished with state failed, but the task instance's 
state attribute is queued. ...
   04:33:15.203 [info ] Marking task as FAILED. 
dag_id=test_k8s_executor_config_legacy, task_id=check_for_failure, ...
   04:33:15.232 [info ] Marking run <DagRun test_k8s_executor_config_legacy 
...> failed
   ```
   
   The task UUID is no longer in `self.running`.
   
   <details>
   <summary>Commands used</summary>
   
   ```bash
   breeze k8s deploy-cluster
   breeze k8s deploy-airflow --executor KubernetesExecutor
   
   export 
KUBECONFIG=$PWD/.build/.k8s-clusters/airflow-python-3.12-v1.31.12/.kubeconfig
   afs() { kubectl -n airflow exec deploy/airflow-scheduler -c scheduler -- \
           env AIRFLOW__LOGGING__LOGGING_LEVEL=ERROR "$@"; }
   
   DP=$(kubectl -n airflow get pod -l component=dag-processor -o name | head -1)
   kubectl -n airflow cp test_k8s_executor_config_legacy.py \
     ${DP#pod/}:/opt/airflow/dags/test_k8s_executor_config_legacy.py -c 
dag-processor
   
   afs airflow dags unpause test_k8s_executor_config_legacy
   afs airflow dags trigger test_k8s_executor_config_legacy -r repro_after_fix
   afs airflow tasks states-for-dag-run test_k8s_executor_config_legacy 
repro_after_fix -o json
   
   kubectl -n airflow logs deploy/airflow-scheduler -c scheduler --since=5m \
     | grep -E "Invalid executor_config|Received executor event|reported that 
the task instance|Marking task as FAILED|self.running"
   
   breeze k8s tests -- -k test_kubernetes_executor
   ```
   
   </details>
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes (please specify the tool below)
   
   Generated-by: Claude Code (Opus 5.5) following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
   


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

Reply via email to