Skip to content

Parse native Lang-SDK Dags in the Dag processor with their runtime - #74035

Merged
jason810496 merged 24 commits into
mainfrom
jason/lang-sdk-e2e/03-native-dag-parse
Oct 5, 2026
Merged

jason810496 merged 24 commits into
mainfrom
jason/lang-sdk-e2e/03-native-dag-parse

Conversation

@jason810496

@jason810496 jason810496 commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Stack (bottom to top): #74042, #74035, #74043, #74036, #74037, #73845, #73846, #73847

Why

Nothing can parse a Dag written entirely in a Lang SDK yet. This is the Dag processor side of the parse_dag half of #71929.

What changes

  • Routing. The manager parses a file whose importer is a CoordinatorDagImporter with the new LangSDKDagFileProcessorProcess (dag_processing/lang_sdk_processor.py), and every other file as before. A callback for such a file is dropped with a warning before the file is queued.
  • Parse child. The forked child resolves get_dagbag_import_timeout for the file, asks the file's importer for its coordinator and execs the runtime, which connects back to the Dag processor over TCP and answers the parse request. Fork, the macOS exec path and log forwarding work as for a Python file (Move the Dag file processor's shared plumbing into a base class #74040).
  • Result. Each returned Dag gets fill_config_defaults and must pass validate_serialized_dag (Validate a serialized Dag and fill its unset settings from the Airflow config #74041). A failed start, a missing result, an invalid frame or message, an invalid Dag, or a parse past its timeout gives an import error. A runtime still running 5s after its result is killed, with anything left in its process group. A native Dag gets the same multi-team executor and pool checks as a Python Dag. The file's Dag source is read with its Dag importer.
  • Dag bag. A Dag bag cannot parse such a file yet: the importer reports an import error, and sync_bag_to_db leaves the file's import errors to the Dag processor. Parse native Lang-SDK Dags in a Dag bag #74043 adds the Dag bag path.
  • Worker. A Python worker handed a task of a native Dag fails it without retries, before it builds a Dag bag. The error names the coordinator class to route the Dag's queue to.
class NodeDagImporter(CoordinatorDagImporter):
    coordinator_classpath = "airflow.sdk.coordinators.node.NodeCoordinator"
    artifact_suffix = ".min.mjs"
    supported_extensions = [".mjs"]  # a registry routes by the last suffix

    def get_source_code(self, definition): ...


class NodeCoordinator(SubprocessCoordinator):
    def _build_parse_dag_command(self, *, path):
        # parse_dag appends --comm and --logs
        return [self.node_executable, os.fspath(path)], read_bundle(path).supervisor_schema_version

Limitations

  • Cluster policies do not run on a native Dag.
  • Only the checks above run on a native Dag. A rule the Python DAG class enforces, such as that a scheduled Dag's required params have defaults, is not checked.

Deviations from #71929

  • The Python parse child execs the runtime and hands it TCP addresses, so no fd-0 bridge stays in between. parse_dag is on SubprocessCoordinator only.

Follow-ups

  • Java Serde.kt (Java SDK: Serialize native Dags to DagSerialization v3 #71190) still writes the stock values of the five config settings, so a native Java Dag does not get the config values yet. The Go SDK serializer (Support TaskFlow Dag definitions natively in the Go-SDK #70158) writes them too.
  • The Go SDK does not parse native Dags yet: it has no CoordinatorDagImporter, and a packed Go bundle has no file extension to claim.
  • Decision 2 of airflow-core/adr/lang-sdk/0009-provider-operators-as-generated-dsl.md says a provider DSL task runs on a Python worker, which a task of a native Dag cannot do. It needs its own amendment.

Was generative AI tooling used to co-author this PR?

@jason810496
jason810496 added this pull request to stack #74038 October 1, 2026 13:38
@jason810496
jason810496 removed this pull request from stack #74038 October 1, 2026 15:13
@jason810496
jason810496 changed the base branch from jason/lang-sdk-e2e/02b-shared-dag-cycle-detection to jason/lang-sdk-e2e/03c-coordinator-dag-parsing October 1, 2026 15:15
@jason810496
jason810496 force-pushed the jason/lang-sdk-e2e/03-native-dag-parse branch from 6f91c3e to 1610ac7 Compare October 1, 2026 15:15
@jason810496
jason810496 added this pull request to stack #74044 October 1, 2026 15:18
@jason810496 jason810496 changed the title Parse native Lang-SDK Dags with their coordinator's runtime Parse native Lang-SDK Dags in the Dag processor with their runtime Oct 1, 2026
Comment thread airflow-core/src/airflow/dag_processing/manager.py Outdated
Comment thread airflow-core/src/airflow/dag_processing/lang_sdk_processor.py Outdated
Comment thread airflow-core/src/airflow/dag_processing/importer_routing.py Outdated
Comment thread airflow-core/src/airflow/dag_processing/importer_routing.py Outdated
Comment thread task-sdk/src/airflow/sdk/execution_time/task_runner.py Outdated
Comment thread airflow-core/src/airflow/dag_processing/manager.py Outdated
Comment thread task-sdk/tests/task_sdk/execution_time/test_task_runner.py Outdated
Comment thread task-sdk/tests/task_sdk/execution_time/test_task_runner.py Outdated
Comment thread airflow-core/tests/unit/dag_processing/test_dagbag.py
Comment thread airflow-core/tests/unit/dag_processing/test_lang_sdk_processor.py Outdated
@jason810496
jason810496 force-pushed the jason/lang-sdk-e2e/03-native-dag-parse branch from 1610ac7 to cd38d33 Compare October 2, 2026 00:54
@jason810496
jason810496 force-pushed the jason/lang-sdk-e2e/03-native-dag-parse branch 2 times, most recently from b2ce95c to e072db6 Compare October 2, 2026 05:44
@pierrejeambrun
pierrejeambrun force-pushed the jason/lang-sdk-e2e/03-native-dag-parse branch from e072db6 to 31c1a6b Compare October 2, 2026 09:00
@jason810496
jason810496 force-pushed the jason/lang-sdk-e2e/03-native-dag-parse branch from ea57472 to a70eb43 Compare October 2, 2026 11:41
The Task SDK now owns the lookup of the coordinator whose runtime
parses a file, so the task runner can share it. Core keeps its
logging wrapper. Its runtime SDK import is recorded in the SDK import
inventory instead of being hidden with noqa, and the broken-config
test uses a real clashing config.
The error now names the coordinator that parses the Dag and suggests
giving the Dag's tasks their own queue, since mapping a shared queue
such as default would send every Python task on it to the runtime.
The task reports FAILED instead of exiting 1, so it does not retry a
misconfiguration that retrying cannot fix. A broken coordinators
config leaves the file to the Dag bag, which reports it.
A schema validation error now names the path of the invalid field.
A process the runtime started could keep its output open after the
runtime exited. Every kill then returned early, so the parse never
finished, its result was lost and the leftover kept running. Once the
runtime has exited, the grace kill, the timeout kill and close() now
kill its process group. The kill also waits a bounded time, so a
runtime stuck in the kernel does not stall the Dag processor loop.
The import error is all a user sees of a timed-out parse, so it now
names the limit to change: [core] dagbag_import_timeout or the
get_dagbag_import_timeout policy, or [dag_processor]
dag_file_processor_timeout before the runtime reports its import
timeout.
Tests assert through these mocks, so a misspelt method would pass.
A Python Dag's tasks are checked for an executor available to the
bundle's team, and tasks in the default pool move to the team's
default pool. A Lang-SDK runtime's Dags now get the same: a Dag with
an unavailable executor is the file's import error, and the pools are
set in the stored payload. The Dag bag's helpers share the bundle team
lookup so the processor can reuse them.
The test commits import error rows that the session fixture does not
roll back, so they leaked into later tests.
The Dag processor manager starts the Lang-SDK processor for a
coordinator-claimed file itself, which differs from the importer
integration point the ADR describes. Record it as a bridge until
importers own their parse process, and why.
Nothing here starts the Lang-SDK processor without an API client: the
manager always passes one, and the first client-less caller is the
Dag bag in a later change. Remove run(), the optional client and the
gate that answered requests with an error without one, so the change
that adds the caller can add them with it.
SIGKILL is delivered asynchronously, so the leftover can still show as
running right after the kill.
CoordinatorManager.for_bundle now returns Dag importers, so the task
runner matches the claiming coordinator through each importer.
The Dag processor always gives the Lang-SDK processor one.
Discovery now returns DagFileInfo records instead of relative paths.
A task of a native Dag that reaches a Python worker now fails with the
coordinator class its Dag importer names, instead of the key of the
coordinator that parses the Dag. The check no longer needs the parsing
coordinator, so it also holds when several coordinators of that class
are configured.
…sing

get_claiming_importer replaces get_claiming_coordinator, and the Task SDK's
find_claiming_coordinator is gone. The manager and the Dag bag sync only
need to know whether an importer claims a file.

The parse child asks the importer for its parsing coordinator, so a Dag
bundle with no usable coordinator is the file's import error. The Dag
source is read through the importer.

The fake Lang-SDK test helpers set coordinator_classpath and register
their importer in COORDINATOR_DAG_IMPORTERS.
A Dag file that no single coordinator can parse stays claimed by its
importer, and its parse fails with an import error that says why: no entry
in dag_bundle_to_coordinator, an entry for another class or one that
cannot be loaded, or an option that is not a JSON object of coordinator keys.
validate_serialized_dag returns the Dag it deserialized, so the Lang-SDK
processor uses it for the executor and pool checks instead of copying and
deserializing the serialized Dag a second time.
@jason810496
jason810496 force-pushed the jason/lang-sdk-e2e/03-native-dag-parse branch from 79bef76 to 9007b91 Compare October 5, 2026 09:12

@pierrejeambrun pierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM just one thing to address I believe.

Comment thread airflow-core/src/airflow/dag_processing/lang_sdk_processor.py
`_register_comm` can run after the runtime has already died, so its `send_msg` call needs the same
BrokenPipeError/ConnectionResetError guard `_service_processor_sockets` uses elsewhere.

Co-Authored-By: Claude <noreply@anthropic.com>

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

Comment thread airflow-core/src/airflow/dag_processing/lang_sdk_processor.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants