blob: 6b12ddd0a1418ca7066b52466c22ea33d8f871d6 [file]
.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
.. http://www.apache.org/licenses/LICENSE-2.0
.. Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
Task execution architecture
===========================
This page is developer-facing background on what happens *inside a worker when a task actually runs* — how the
Task SDK, the Supervisor and Coordinator processes, and the language runtimes work together: which processes
are involved, and the classes and protocols they use to communicate. It complements
`Adding a new language SDK <30_new_language_sdk.rst>`__, which covers how to implement the coordinator side of
a new runtime.
End users do not need any of this: they only configure a queue and write tasks. The user-facing guides live
under `Non-Python Task SDKs <../airflow-core/docs/authoring-and-scheduling/language-sdks/index.rst>`__.
.. contents:: Table of Contents
:local:
:depth: 2
Python Task SDK execution
-------------------------
When a *worker* actually runs a task, it does not run the user's code directly. Instead it starts a
lightweight **Supervisor** that runs in its own **native operating-system process** and
*forks* a second native process in which the **Task SDK** runtime (``task_runner``) executes the user code.
The two processes talk over a socket, and the Supervisor is the only side that ever holds the short-lived
task JWT or talks to the *Execution API* — the user's code never sees the token and never touches the
database.
The same runtime can also run *in-process* (a single Python process, no fork, no sockets, no HTTP) for
``dag.test()`` and local runs. The diagram below contrasts the two paths and marks where each Python process
lives:
.. image:: images/diagram_task_sdk_execution_architecture.png
The message flow of a supervised run — startup, running the user code, proxied Connection/Variable/XCom
lookups, heartbeats, and reporting the final state — is shown below as a sequence diagram, with each process
on its own lifeline. The **Supervisor** sits in the middle, so the Task ↔ Supervisor request/response
round-trip (the task asks for a Connection/Variable/XCom and gets the answer back) reads as arrows going back
and forth between neighboring lifelines. Each arrow is numbered, colored by its sender, and labeled with the
message class or protocol used:
.. image:: images/diagram_task_sdk_execution_sequence.png
Non-Python language SDKs (Go and Java)
--------------------------------------
The Task Execution Interface (TEI) introduced in AIP-72 is language-agnostic, so a task can also be written in
a **compiled, non-Python language**. A Python Dag still declares the task with ``@task.stub(queue=...)`` (so
Python and non-Python tasks can be mixed in one Dag), but the actual work is delegated to the matching runtime.
The **first-class** integration is the **Coordinator** layer — the Python Supervisor drives the language runtime
as a subprocess and proxies every Execution-API call for it — and it is the shared direction for the Java, Go,
and upcoming language SDKs.
**Coordinator (Java and Go).** Both the
`Java SDK <../airflow-core/docs/authoring-and-scheduling/language-sdks/java.rst>`__ and the
`Go SDK <../airflow-core/docs/authoring-and-scheduling/language-sdks/go.rst>`__ *reuse* the existing Python
Supervisor through the **Coordinator** layer. ``CoordinatorManager`` resolves the task's ``queue`` to a
``BaseCoordinator`` — a ``SubprocessCoordinator`` (``JavaCoordinator`` for the ``java`` queue,
``ExecutableCoordinator`` for the Go bundle queue, e.g. ``golang``), or the built-in ``_PythonCoordinator``
otherwise. The coordinator opens two loopback-TCP servers, spawns the language **bundle subprocess**
(``java -classpath ... <main class>`` for Java, or the self-contained packed bundle binary for Go) with
``--comm`` / ``--logs`` appended, and drives it with ``_PopenActivitySubprocess`` (a subclass of the shared
``ActivitySubprocess``). The subprocess connects *back* over TCP and speaks the **same msgpack protocol** as a
Python task, so the Python side heartbeats, manages state, and **proxies every Execution-API call** — meaning
the language task, like a Python task, never holds the task JWT itself. Because the mature Python Supervisor
handles the Airflow-facing concerns, this mode inherits its capabilities — remote task logs (S3/GCS, etc.), the
full range of task states, and alternate XCom backends:
.. image:: images/diagram_coordinator_execution_architecture.png
The end-to-end workflow of a coordinator task — from ``@task.stub`` through the coordinator, the language
subprocess, the proxied Connection/Variable/XCom lookups, and reporting the final state — is shown below as a
sequence diagram. The **Supervisor** is the central lifeline, so the subprocess ↔ Supervisor round-trip over
loopback TCP is drawn as arrows going back and forth to its neighbours:
.. image:: images/diagram_coordinator_execution_sequence.png
.. note::
Both the Go and Java SDKs are **experimental** and under active development. See the
`Non-Python Task SDKs guide <../airflow-core/docs/authoring-and-scheduling/language-sdks/index.rst>`__ —
the `Go SDK <../airflow-core/docs/authoring-and-scheduling/language-sdks/go.rst>`__ and
`Java SDK <../airflow-core/docs/authoring-and-scheduling/language-sdks/java.rst>`__ pages — for current
status, quick-starts, and known limitations.
------
To implement the coordinator side of a new runtime, continue to
`Adding a new language SDK <30_new_language_sdk.rst>`__.