Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 17.8k
AIP-72: Port task success overtime to the Supervisor#44590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -51,6 +51,7 @@ | ||
| GetVariable, | ||
| GetXCom, | ||
| StartupDetails, | ||
| TaskState, | ||
| ToSupervisor, | ||
| ) | ||
| @@ -265,9 +266,9 @@ class WatchedSubprocess: | ||
| client: Client | ||
| _process: psutil.Process | ||
| _exit_code: int | None = None | ||
| _terminal_state: str | None = None | ||
| _final_state: str | None = None | ||
| _exit_code: int | None = attrs.field(default=None, init=False) | ||
| _terminal_state: str | None = attrs.field(default=None, init=False) | ||
| _final_state: str | None = attrs.field(default=None, init=False) | ||
ashb marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| _last_successful_heartbeat: float = attrs.field(default=0, init=False) | ||
| _last_heartbeat_attempt: float = attrs.field(default=0, init=False) | ||
| @@ -277,6 +278,12 @@ class WatchedSubprocess: | ||
| # does not hang around forever. | ||
| failed_heartbeats: int = attrs.field(default=0, init=False) | ||
| # Maximum possible time (in seconds) that task will have for execution of auxiliary processes | ||
| # like listeners after task is complete. | ||
| # TODO: This should come from airflow.cfg: [core] task_success_overtime | ||
| TASK_OVERTIME_THRESHOLD: ClassVar[float] = 20.0 | ||
| _task_end_time_monotonic: float | None = attrs.field(default=None, init=False) | ||
| selector: selectors.BaseSelector = attrs.field(factory=selectors.DefaultSelector) | ||
| procs: ClassVar[weakref.WeakValueDictionary[int, WatchedSubprocess]] = weakref.WeakValueDictionary() | ||
| @@ -500,6 +507,21 @@ def _monitor_subprocess(self): | ||
| self._send_heartbeat_if_needed() | ||
| self._handle_task_overtime_if_needed() | ||
| def _handle_task_overtime_if_needed(self): | ||
| """Handle termination of auxiliary processes if the task exceeds the configured overtime.""" | ||
| # If the task has reached a terminal state, we can start monitoring the overtime | ||
| if not self._terminal_state: | ||
| return | ||
| if ( | ||
| self._task_end_time_monotonic | ||
| and (time.monotonic() - self._task_end_time_monotonic) > self.TASK_OVERTIME_THRESHOLD | ||
| ): | ||
| log.warning("Task success overtime reached; terminating process", ti_id=self.ti_id) | ||
| self.kill(signal.SIGTERM, force=True) | ||
| def _service_subprocess(self, max_wait_time: float, raise_on_timeout: bool = False): | ||
| """ | ||
| Service subprocess events by processing socket activity and checking for process exit. | ||
| @@ -631,9 +653,11 @@ def handle_requests(self, log: FilteringBoundLogger) -> Generator[None, bytes, N | ||
| log.exception("Unable to decode message", line=line) | ||
| continue | ||
| # if isinstance(msg, TaskState): | ||
| # self._terminal_state = msg.state | ||
| if isinstance(msg, GetConnection): | ||
| resp = None | ||
| if isinstance(msg, TaskState): | ||
| self._terminal_state = msg.state | ||
| self._task_end_time_monotonic = time.monotonic() | ||
| elif isinstance(msg, GetConnection): | ||
| conn = self.client.connections.get(msg.conn_id) | ||
| resp = conn.model_dump_json(exclude_unset=True).encode() | ||
| elif isinstance(msg, GetVariable): | ||
| @@ -645,7 +669,6 @@ def handle_requests(self, log: FilteringBoundLogger) -> Generator[None, bytes, N | ||
| elif isinstance(msg, DeferTask): | ||
| self._terminal_state = IntermediateTIState.DEFERRED | ||
| self.client.task_instances.defer(self.ti_id, msg) | ||
| resp = None | ||
| else: | ||
| log.error("Unhandled request", msg=msg) | ||
| continue | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -21,16 +21,17 @@ | ||
| import os | ||
| import sys | ||
| from datetime import datetime, timezone | ||
| from io import FileIO | ||
| from typing import TYPE_CHECKING, TextIO | ||
| import attrs | ||
| import structlog | ||
| from pydantic import ConfigDict, TypeAdapter | ||
| from airflow.sdk.api.datamodels._generated import TaskInstance | ||
| from airflow.sdk.api.datamodels._generated import TaskInstance, TerminalTIState | ||
| from airflow.sdk.definitions.baseoperator import BaseOperator | ||
| from airflow.sdk.execution_time.comms import DeferTask, StartupDetails, ToSupervisor, ToTask | ||
| from airflow.sdk.execution_time.comms import DeferTask, StartupDetails, TaskState, ToSupervisor, ToTask | ||
| if TYPE_CHECKING: | ||
| from structlog.typing import FilteringBoundLogger as Logger | ||
| @@ -158,11 +159,14 @@ def run(ti: RuntimeTaskInstance, log: Logger): | ||
| if TYPE_CHECKING: | ||
| assert ti.task is not None | ||
| assert isinstance(ti.task, BaseOperator) | ||
| msg: ToSupervisor | None = None | ||
| try: | ||
| # TODO: pre execute etc. | ||
| # TODO next_method to support resuming from deferred | ||
| # TODO: Get a real context object | ||
| ti.task.execute({"task_instance": ti}) # type: ignore[attr-defined] | ||
| msg = TaskState(state=TerminalTIState.SUCCESS, end_date=datetime.now(tz=timezone.utc)) | ||
| except TaskDeferred as defer: | ||
| classpath, trigger_kwargs = defer.trigger.serialize() | ||
| next_method = defer.method_name | ||
| @@ -173,7 +177,6 @@ def run(ti: RuntimeTaskInstance, log: Logger): | ||
| next_method=next_method, | ||
| trigger_timeout=timeout, | ||
| ) | ||
| SUPERVISOR_COMMS.send_request(msg=msg, log=log) | ||
| except AirflowSkipException: | ||
| ... | ||
| except AirflowRescheduleException: | ||
| @@ -189,6 +192,9 @@ def run(ti: RuntimeTaskInstance, log: Logger): | ||
| # TODO: Handle TI handle failure | ||
| raise | ||
| if msg: | ||
| SUPERVISOR_COMMS.send_request(msg=msg, log=log) | ||
kaxil marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| def finalize(log: Logger): ... | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -27,8 +27,8 @@ | ||
| from uuid6 import uuid7 | ||
| from airflow.sdk import DAG, BaseOperator | ||
| from airflow.sdk.api.datamodels._generated import TaskInstance | ||
| from airflow.sdk.execution_time.comms import DeferTask, StartupDetails | ||
| from airflow.sdk.api.datamodels._generated import TaskInstance, TerminalTIState | ||
| from airflow.sdk.execution_time.comms import DeferTask, StartupDetails, TaskState | ||
| from airflow.sdk.execution_time.task_runner import CommsDecoder, parse, run | ||
| from airflow.utils import timezone | ||
| @@ -78,7 +78,7 @@ def test_parse(test_dags_dir: Path): | ||
| assert isinstance(ti.task.dag, DAG) | ||
| def test_run_basic(test_dags_dir: Path): | ||
| def test_run_basic(test_dags_dir: Path, time_machine): | ||
| """Test running a basic task.""" | ||
| what = StartupDetails( | ||
| ti=TaskInstance(id=uuid7(), task_id="hello", dag_id="super_basic_run", run_id="c", try_number=1), | ||
| @@ -87,7 +87,19 @@ def test_run_basic(test_dags_dir: Path): | ||
| ) | ||
| ti = parse(what) | ||
| run(ti, log=mock.MagicMock()) | ||
| instant = timezone.datetime(2024, 12, 3, 10, 0) | ||
| time_machine.move_to(instant, tick=False) | ||
| with mock.patch( | ||
| "airflow.sdk.execution_time.task_runner.SUPERVISOR_COMMS", create=True | ||
| ) as mock_supervisor_comms: | ||
| mock_supervisor_comms.send_request = mock.Mock() | ||
| run(ti, log=mock.MagicMock()) | ||
| mock_supervisor_comms.send_request.assert_called_once_with( | ||
| msg=TaskState(state=TerminalTIState.SUCCESS, end_date=instant), log=mock.ANY | ||
| ) | ||
ashb marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| def test_run_deferred_basic(test_dags_dir: Path, time_machine): | ||
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.