Skip to content

Timeout Airflow TI and Sensors when using deferable mode - #32990

Closed
hussein-awala wants to merge 23 commits into
apache:mainfrom
hussein-awala:deferred_task_timeout
Closed

Timeout Airflow TI and Sensors when using deferable mode#32990
hussein-awala wants to merge 23 commits into
apache:mainfrom
hussein-awala:deferred_task_timeout

Conversation

@hussein-awala

@hussein-awala hussein-awala commented Aug 1, 2023

Copy link
Copy Markdown
Member

closes: #32638

This PR handles the trigger timeout and raise AirflowTaskTimeout or AirflowSensorTimeout instead of failing the TI with TaskDeferralError.

It deprecates providing a custom timeout, where it calculate the trigger timeout based on the execution timeout and/or the sensor timeout.

To test the sensor timeout case, I used:

import pendulum

from airflow import DAG
from airflow.sensors.time_delta import TimeDeltaSensorAsync

with DAG(
    dag_id="trigger_timeout",
    schedule_interval=None,
    start_date=pendulum.datetime(2023, 8, 1),
):
    TimeDeltaSensorAsync(
        task_id="test_timeout",
        delta=pendulum.duration(minutes=1),
        timeout=pendulum.duration(seconds=10),
    )

and here is the log:

[2023-08-03, 21:39:29 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=non-requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:39:27.882355+00:00 [queued]>
[2023-08-03, 21:39:29 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:39:27.882355+00:00 [queued]>
[2023-08-03, 21:39:29 UTC] {taskinstance.py:1371} INFO - Starting attempt 1 of 4
[2023-08-03, 21:39:29 UTC] {taskinstance.py:1392} INFO - Executing <Task(TimeDeltaSensorAsync): test_timeout> on 2023-08-03 21:39:27.882355+00:00
[2023-08-03, 21:39:29 UTC] {standard_task_runner.py:57} INFO - Started process 5024 to run task
[2023-08-03, 21:39:29 UTC] {standard_task_runner.py:84} INFO - Running: ['***', 'tasks', 'run', 'trigger_timeout', 'test_timeout', 'manual__2023-08-03T21:39:27.882355+00:00', '--job-id', '10', '--raw', '--subdir', 'DAGS_FOLDER/trigger_timeout.py', '--cfg-path', '/tmp/tmpkfelpjao']
[2023-08-03, 21:39:29 UTC] {standard_task_runner.py:85} INFO - Job 10: Subtask test_timeout
[2023-08-03, 21:39:29 UTC] {task_command.py:415} INFO - Running <TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:39:27.882355+00:00 [running]> on host b191457cb709
[2023-08-03, 21:39:29 UTC] {taskinstance.py:1670} INFO - Exporting env vars: AIRFLOW_CTX_DAG_OWNER='***' AIRFLOW_CTX_DAG_ID='trigger_timeout' AIRFLOW_CTX_TASK_ID='test_timeout' AIRFLOW_CTX_EXECUTION_DATE='2023-08-03T21:39:27.882355+00:00' AIRFLOW_CTX_TRY_NUMBER='1' AIRFLOW_CTX_DAG_RUN_ID='manual__2023-08-03T21:39:27.882355+00:00'
[2023-08-03, 21:39:29 UTC] {taskinstance.py:1536} INFO - Pausing task as DEFERRED. dag_id=trigger_timeout, task_id=test_timeout, execution_date=20230803T213927, start_date=20230803T213929
[2023-08-03, 21:39:29 UTC] {local_task_job_runner.py:225} INFO - Task exited with return code 100 (task deferral)
[2023-08-03, 21:39:31 UTC] {temporal.py:60} INFO - trigger starting
[2023-08-03, 21:39:31 UTC] {temporal.py:64} INFO - 56 seconds remaining; sleeping 10 seconds
[2023-08-03, 21:39:40 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=non-requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:39:27.882355+00:00 [queued]>
[2023-08-03, 21:39:40 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:39:27.882355+00:00 [queued]>
[2023-08-03, 21:39:40 UTC] {taskinstance.py:1369} INFO - Resuming after deferral
[2023-08-03, 21:39:40 UTC] {taskinstance.py:1392} INFO - Executing <Task(TimeDeltaSensorAsync): test_timeout> on 2023-08-03 21:39:27.882355+00:00
[2023-08-03, 21:39:40 UTC] {standard_task_runner.py:57} INFO - Started process 5031 to run task
[2023-08-03, 21:39:40 UTC] {standard_task_runner.py:84} INFO - Running: ['***', 'tasks', 'run', 'trigger_timeout', 'test_timeout', 'manual__2023-08-03T21:39:27.882355+00:00', '--job-id', '11', '--raw', '--subdir', 'DAGS_FOLDER/trigger_timeout.py', '--cfg-path', '/tmp/tmpncdko_kp']
[2023-08-03, 21:39:40 UTC] {standard_task_runner.py:85} INFO - Job 11: Subtask test_timeout
[2023-08-03, 21:39:40 UTC] {task_command.py:415} INFO - Running <TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:39:27.882355+00:00 [running]> on host b191457cb709
[2023-08-03, 21:39:40 UTC] {taskinstance.py:1947} ERROR - Task failed with exception
airflow.exceptions.AirflowSensorTimeout: Trigger/execution timeout
[2023-08-03, 21:39:40 UTC] {taskinstance.py:1410} INFO - Immediate failure requested. Marking task as FAILED. dag_id=trigger_timeout, task_id=test_timeout, execution_date=20230803T213927, start_date=20230803T213929, end_date=20230803T213940
[2023-08-03, 21:39:40 UTC] {standard_task_runner.py:104} ERROR - Failed to execute job 11 for task test_timeout (Trigger/execution timeout; 5031)
[2023-08-03, 21:39:40 UTC] {local_task_job_runner.py:228} INFO - Task exited with return code 1
[2023-08-03, 21:39:40 UTC] {taskinstance.py:2788} INFO - 0 downstream tasks scheduled from follow-on schedule check
[2023-08-03, 21:39:41 UTC] {temporal.py:64} INFO - 46 seconds remaining; sleeping 10 seconds
[2023-08-03, 21:39:41 UTC] {triggerer_job_runner.py:605} ERROR - Trigger cancelled due to timeout
[2023-08-03, 21:39:41 UTC] {triggerer_job_runner.py:606} ERROR - Trigger cancelled; message=

And it was passed directly to Failed although there were 3 more attempts (this is the expected behavior).

And for the execution_timeout:

import pendulum

from airflow import DAG
from airflow.sensors.time_delta import TimeDeltaSensorAsync

with DAG(
    dag_id="trigger_timeout",
    schedule_interval=None,
    start_date=pendulum.datetime(2023, 8, 1),
):
    TimeDeltaSensorAsync(
        task_id="test_timeout",
        delta=pendulum.duration(minutes=1),
        execution_timeout=pendulum.duration(seconds=10),
        retries=3,
    )

and here is the log:

[2023-08-03, 21:37:09 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=non-requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:37:08.168874+00:00 [queued]>
[2023-08-03, 21:37:09 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:37:08.168874+00:00 [queued]>
[2023-08-03, 21:37:09 UTC] {taskinstance.py:1371} INFO - Starting attempt 1 of 4
[2023-08-03, 21:37:09 UTC] {taskinstance.py:1392} INFO - Executing <Task(TimeDeltaSensorAsync): test_timeout> on 2023-08-03 21:37:08.168874+00:00
[2023-08-03, 21:37:09 UTC] {standard_task_runner.py:57} INFO - Started process 3975 to run task
[2023-08-03, 21:37:09 UTC] {standard_task_runner.py:84} INFO - Running: ['***', 'tasks', 'run', 'trigger_timeout', 'test_timeout', 'manual__2023-08-03T21:37:08.168874+00:00', '--job-id', '8', '--raw', '--subdir', 'DAGS_FOLDER/trigger_timeout.py', '--cfg-path', '/tmp/tmp90cs9sii']
[2023-08-03, 21:37:09 UTC] {standard_task_runner.py:85} INFO - Job 8: Subtask test_timeout
[2023-08-03, 21:37:09 UTC] {task_command.py:415} INFO - Running <TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:37:08.168874+00:00 [running]> on host b191457cb709
[2023-08-03, 21:37:09 UTC] {taskinstance.py:1670} INFO - Exporting env vars: AIRFLOW_CTX_DAG_OWNER='***' AIRFLOW_CTX_DAG_ID='trigger_timeout' AIRFLOW_CTX_TASK_ID='test_timeout' AIRFLOW_CTX_EXECUTION_DATE='2023-08-03T21:37:08.168874+00:00' AIRFLOW_CTX_TRY_NUMBER='1' AIRFLOW_CTX_DAG_RUN_ID='manual__2023-08-03T21:37:08.168874+00:00'
[2023-08-03, 21:37:09 UTC] {taskinstance.py:1536} INFO - Pausing task as DEFERRED. dag_id=trigger_timeout, task_id=test_timeout, execution_date=20230803T213708, start_date=20230803T213709
[2023-08-03, 21:37:09 UTC] {local_task_job_runner.py:225} INFO - Task exited with return code 100 (task deferral)
[2023-08-03, 21:37:10 UTC] {temporal.py:60} INFO - trigger starting
[2023-08-03, 21:37:10 UTC] {temporal.py:64} INFO - 57 seconds remaining; sleeping 10 seconds
[2023-08-03, 21:37:20 UTC] {temporal.py:64} INFO - 47 seconds remaining; sleeping 10 seconds
[2023-08-03, 21:37:25 UTC] {triggerer_job_runner.py:605} ERROR - Trigger cancelled due to timeout
[2023-08-03, 21:37:25 UTC] {triggerer_job_runner.py:606} ERROR - Trigger cancelled; message=
[2023-08-03, 21:37:25 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=non-requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:37:08.168874+00:00 [queued]>
[2023-08-03, 21:37:25 UTC] {taskinstance.py:1169} INFO - Dependencies all met for dep_context=requeueable deps ti=<TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:37:08.168874+00:00 [queued]>
[2023-08-03, 21:37:25 UTC] {taskinstance.py:1369} INFO - Resuming after deferral
[2023-08-03, 21:37:25 UTC] {taskinstance.py:1392} INFO - Executing <Task(TimeDeltaSensorAsync): test_timeout> on 2023-08-03 21:37:08.168874+00:00
[2023-08-03, 21:37:25 UTC] {standard_task_runner.py:57} INFO - Started process 4182 to run task
[2023-08-03, 21:37:25 UTC] {standard_task_runner.py:84} INFO - Running: ['***', 'tasks', 'run', 'trigger_timeout', 'test_timeout', 'manual__2023-08-03T21:37:08.168874+00:00', '--job-id', '9', '--raw', '--subdir', 'DAGS_FOLDER/trigger_timeout.py', '--cfg-path', '/tmp/tmp3k5mppir']
[2023-08-03, 21:37:25 UTC] {standard_task_runner.py:85} INFO - Job 9: Subtask test_timeout
[2023-08-03, 21:37:25 UTC] {task_command.py:415} INFO - Running <TaskInstance: trigger_timeout.test_timeout manual__2023-08-03T21:37:08.168874+00:00 [running]> on host b191457cb709
[2023-08-03, 21:37:25 UTC] {taskinstance.py:1947} ERROR - Task failed with exception
airflow.exceptions.AirflowTaskTimeout: Trigger/execution timeout
[2023-08-03, 21:37:25 UTC] {taskinstance.py:1410} INFO - Marking task as UP_FOR_RETRY. dag_id=trigger_timeout, task_id=test_timeout, execution_date=20230803T213708, start_date=20230803T213709, end_date=20230803T213725
[2023-08-03, 21:37:25 UTC] {standard_task_runner.py:104} ERROR - Failed to execute job 9 for task test_timeout (Trigger/execution timeout; 4182)
[2023-08-03, 21:37:25 UTC] {local_task_job_runner.py:228} INFO - Task exited with return code 1
[2023-08-03, 21:37:25 UTC] {taskinstance.py:2788} INFO - 0 downstream tasks scheduled from follow-on schedule check

And it was passed to up_for_retry.


^ Add meaningful description above

Read the Pull Request Guidelines for more information.
In case of fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in a newsfragment file, named {pr_number}.significant.rst or {issue_number}.significant.rst, in newsfragments.

@boring-cyborg boring-cyborg Bot added area:core-operators area:providers area:Scheduler including HA (high availability) scheduler provider:amazon AWS/Amazon - related issues provider:dbt-cloud provider:google Google (including GCP) related issues provider:microsoft-azure Azure-related issues provider:snowflake Issues related to Snowflake provider provider:apache-livy labels Aug 1, 2023
@hussein-awala hussein-awala changed the title [WIP] Timeout Airflow TI and Sensors when using defferable mode Timeout Airflow TI and Sensors when using defferable mode Aug 3, 2023
@hussein-awala
hussein-awala marked this pull request as ready for review August 3, 2023 22:01
@dstandish

Copy link
Copy Markdown
Contributor

You are assuming that the defer timeout is always <overall timeout> - <time elapsed since start> but should we assume that? Should we not permit user to defer for a specified timeout, which is supported by present design?

@dstandish

Copy link
Copy Markdown
Contributor

I should add... I believe (or at least i have always assumed) that the overall task timeout has always already been enforced. but the trigger timeout is something different that you may optionally use if you want to put a timeout on the specific deferral. So if I understand correctly, you're simply removing the feature to optionally put a timeout on a deferral. Is that correct? Is it really necessary / desirable?

Comment thread airflow/models/taskinstance.py Outdated
Comment on lines 169 to 174

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.

Why do we need to move this? The import is done locally anyway and from what I can tell it can live where it has been.

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.

Currently I duplicated it, but I think we should move it to TI (or base operator class), since it is useful in all the operators and not just in Python operators. WDYT?

Comment on lines 94 to 98

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.

This entire block is redundant now.

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.

Oops, I will fix it

@uranusjr

uranusjr commented Aug 4, 2023

Copy link
Copy Markdown
Member

How would removing the timeout parameter from providers affect how they work with older versions of Airflow?

@hussein-awala

Copy link
Copy Markdown
Member Author

You are assuming that the defer timeout is always - but should we assume that?

If we want to apply the same logic used in the normal mode for the execution_timeout the answer will be yes.

Should we not permit user to defer for a specified timeout, which is supported by present design?

I can remove the deprecation warning and keep the support of custom timeout for the trigger

I believe (or at least i have always assumed) that the overall task timeout has always already been enforced. but the trigger timeout is something different that you may optionally use if you want to put a timeout on the specific deferral. So if I understand correctly, you're simply removing the feature to optionally put a timeout on a deferral. Is that correct? Is it really necessary / desirable?

You assumption is not completely correct; there was already a timeout enforced equals to execution_timeout, but:

  • it starts from the moment when we defer the task and not from the start_date
  • the task raise a trigger exception when the trigger is timeouted and not task timeout exception (which stop the execution)
  • the sensor timeout was not respected: it's configured by the parameter timeout in base sensor which stops the current attempt and give the task another change. I thought that we allow the user to define a trigger timeout as a workaround for this case, but it appears that it is not the case.

@hussein-awala

Copy link
Copy Markdown
Member Author

How would removing the timeout parameter from providers affect how they work with older versions of Airflow?

You're right, it is not b/c, I will update it.

Comment thread airflow/exceptions.py Outdated
Comment on lines +365 to +366
trigger_timeout: datetime.datetime | None = None,
trigger_timeout_reason: str | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this seems to me like breaking backcompat

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

specifically, you are removing the timeout param.
why do we need to remove that?
why do we need to add trigger_ prefix? isn't it implied?

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.

I agree, I ignored the possibility that the user might use it in his operators or unit tests. I will replace trigger_timeout and trigger_timeout_reason by timeout and timeout_reason.

@dstandish

Copy link
Copy Markdown
Contributor

So, what was the case before this change? IIUC:

  • execution_timeout applied to the overall execution of the task (e.g. duration of a single run of execute)
  • task deferral timeout is applied to the duration of a single deferral, and ignores overal execution_timeout
  • if task comes out of deferral then the execution_timeout is applied again to the task from the moment it resumes exeuction. so that for example if a task runs, defers, and resumes, and the execution timeout is 1 hr, then the task could run for 1 hr + deferral timeout + 1hr after resuming.

do i have that right?

it makes me a bit uncomfortable to remove the feature to be able to set a specific deferral timeout on a given deferral. not that it's necessarily all that useful, but it's there.

now as to your change, let me see if i understand the new behavior correctly.

  • execution_timeout will be applied to the overall duration of the task instance, no matter how many defer-resume cycles there are.
  • if the execution_timeout is 1 hr, then now if phase 1 takes 10 minutes, the deferral timeout will be set to 50 minutes. then if task is deferred for 30 minutes before resuming, then when task resumes, effective execution timeout will be 30 minutes. and so on.
  • timeout will be ignored if passed to self.defer

do i have all of that correct? or if i got something wrong, please correct. thanks.

@hussein-awala

Copy link
Copy Markdown
Member Author

So, what was the case before this change? IIUC:

execution_timeout applied to the overall execution of the task (e.g. duration of a single run of execute)

yes, but when it's reached, we don't execute on_kill method

task deferral timeout is applied to the duration of a single deferral, and ignores overal execution_timeout

Non, we take the min between deferral timeout and execution_timeout if they are both present

if task comes out of deferral then the execution_timeout is applied again to the task from the moment it resumes exeuction. so that for example if a task runs, defers, and resumes, and the execution timeout is 1 hr, then the task could run for 1 hr + deferral timeout + 1hr after resuming.

Non, when we deferred the task, we calculated timeout as:

        # Calculate timeout too if it was passed
        if defer.timeout is not None:
            self.trigger_timeout = timezone.utcnow() + defer.timeout
        else:
            self.trigger_timeout = None

        # If an execution_timeout is set, set the timeout to the minimum of
        # it and the trigger timeout
        execution_timeout = self.task.execution_timeout
        if execution_timeout:
            if self.trigger_timeout:
                self.trigger_timeout = min(self.start_date + execution_timeout, self.trigger_timeout)
            else:
                self.trigger_timeout = self.start_date + execution_timeout

And when we resumed the task, we calculated the timeout duration as:

                timeout_seconds = (
                    task_to_execute.execution_timeout - (timezone.utcnow() - self.start_date)
                ).total_seconds()

The main issue in the operators (not sensors) was with how we handle this timeout, where it was considered as a normal failure in the trigger.

it makes me a bit uncomfortable to remove the feature to be able to set a specific deferral timeout on a given deferral. not that it's necessarily all that useful, but it's there.

It's still possible, with the possibility to control the callback when this timeout is reached

execution_timeout will be applied to the overall duration of the task instance, no matter how many defer-resume cycles there are.

yes but this was the case before

if the execution_timeout is 1 hr, then now if phase 1 takes 10 minutes, the deferral timeout will be set to 50 minutes. then if task is deferred for 30 minutes before resuming, then when task resumes, effective execution timeout will be 30 minutes. and so on.

20 minutes not 30 (1h - 10m - 30m)

timeout will be ignored if passed to self.defer

if it's the earliest timeout moment, it will be used, and the on_defer_timeout callback will be called, if not, we'll use execution timeout, or sensor timeout (the user can define other custom types of timeout)

The most important thing is the sensor timeout; In sync mode, when this timeout is reached, we fail the task regardless the number of remaining attempts, and this is not the case in the current async mode. This PR fixes this bug.

So in summary, this PR:

  • process the timeout based on its reason
  • if it's because of execution timeout, it fails the attempt and call on_kill
  • if it's because of deferring timeout, it call the new callback method, which by default raise AirflowDeferTimeout, but the user can override it to return values or do anything
  • if it's a sensor, and the timeout is because of sensor timeout, it fails the TI immediately regards the number of remaining attempts

@hussein-awala hussein-awala removed provider:google Google (including GCP) related issues provider:amazon AWS/Amazon - related issues provider:snowflake Issues related to Snowflake provider provider:dbt-cloud provider:apache-livy labels Aug 23, 2023
@potiuk
potiuk force-pushed the deferred_task_timeout branch from d71f56a to 98a2166 Compare August 23, 2023 09:38
Comment thread airflow/exceptions.py Outdated

@dstandish dstandish Aug 24, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you are changing data type from timedelta to datetime? that would seem to break backcompat.

Comment thread airflow/models/taskinstance.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why doesn't this make more sense on the trigger model?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i see there's already a trigger_timeout column on TI.

does seem to make more sense on trigger. but not sure if we should break with what's there already. just, ti tends to be one of the larger tables, and it's already pretty wide.

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.

does seem to make more sense on trigger. but not sure if we should break with what's there already. just, ti tends to be one of the larger tables, and it's already pretty wide.

it depends on if we decide to handle the timeout in the task instance as I do now, or the trigger cleanup as you propose.

Comment thread airflow/exceptions.py Outdated
Comment on lines 368 to 370

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would seem we should keep this?

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.

I totally agree, I'll update it

Comment thread airflow/models/baseoperator.py Outdated

@dstandish dstandish Aug 24, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it's good to document the params. not necessarily obvious what the param "timeout" means given that, apparently, it can mean different things

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

perhaps instead of adding a column to task instance, sensors could use a subclass of BaseTrigger which would be handled differently, and for that class of trigger, the timeouts would always result in an immediate task fail with no retries.

@dstandish

Copy link
Copy Markdown
Contributor

It feels like we may be rolling too much into one PR here, too many different decisions.

In part, it's a fix for sensors.

In part it's a change to the interface for deferrables.

In part it's ensuring on_kill is called when trigger is cancelled due to execution_timeout.

Regarding on_kill -- are we sure that on_kill should be called? The trigger class has a cleanup method which is called when it is cancelled. Should we not rely on this method instead? Or perhaps it should be delegated to the trigger to determine whether on_kill should be called in this circumstance.

I'm also not sure it is really necessary to change the interface for deferrables and add columns to task_instance in order to fix the issue with sensor retries. It seems possible to fix that with perhaps less invasive changes.

But having all of these changes in one PR makes it feel like it's all or nothing, and hard to assess whether the changes are the right way to solve these different problems. As usual, I won't stand in the way if others disagree, but if it's not too burdensome, it might be easier to make these decisions if we split up the PRs.

Comment thread airflow/models/baseoperator.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we really need to merge these timeouts in this way?

the execution_timeout is still applied to the trigger, it seems.

so, it seems that sensors could always use the sensor timeout value to determine the appropriate deferral timeout to use. and such could be handled differentially by creating a new trigger subclass. and let execution_timeout continue to be applied independenty.

it feels a bit wrong to muddle them together.

@hussein-awala

Copy link
Copy Markdown
Member Author

It feels like we may be rolling too much into one PR here, too many different decisions.
In part, it's a fix for sensors.
In part it's a change to the interface for deferrables.
In part it's ensuring on_kill is called when trigger is cancelled due to execution_timeout.

I don't see a big risk to to all of that in the same time, because this help to avoid changing the interface when we want to fix the other problems (in case we found that there is something we missed during the first PR). But yes this could be split in two PR, one for on_kill (which needs changing in the interface), and as second one for sensor issue.

Regarding on_kill -- are we sure that on_kill should be called? The trigger class has a cleanup method which is called when it is cancelled. Should we not rely on this method instead? Or perhaps it should be delegated to the trigger to determine whether on_kill should be called in this circumstance.

  1. Cleanup method is always called, with and without cancellation, and we cannot change this behavior
  2. Trigger could be ran in different triggerers at the same time, I don't know if cleaning the resources will be safe in this case
  3. Cleanup code could be heavy on the triggerer, where in most of the cases, we use the trigger to run a waiting loop, so maybe adding more extra code is not a good idea. Especially that if the user should implement the same on_kill code using asyn code, which is different, and maybe the lib/client doesn't easily support it.

IMHO making async and sync mode consistent is the best option, but that's of course debatable.

I'm also not sure it is really necessary to change the interface for deferrables and add columns to task_instance in order to fix the issue with sensor retries. It seems possible to fix that with perhaps less invasive changes.

Not sure about deferrables interfaces, but for the new column it is possible, but too complicated, where we should rerun the methods to re-find the reason, I started with this solution before adding the column.

But having all of these changes in one PR makes it feel like it's all or nothing, and hard to assess whether the changes are the right way to solve these different problems. As usual, I won't stand in the way if others disagree, but if it's not too burdensome, it might be easier to make these decisions if we split up the PRs.

I'm open to split them, do you have any suggestion to how we should do that?

@hussein-awala

Copy link
Copy Markdown
Member Author

so, it seems that sensors could always use the sensor timeout value to determine the appropriate deferral timeout to use. and such could be handled differentially by creating a new trigger subclass. and let execution_timeout continue to be applied independenty.

This one also depends on how we decide to handle the timeout, if we decide to handle it in the triggerer (which I don't prefer). But for me, I imagine the TI tell the trigger: "hey, I need your to do X, and you have Y seconds to do that, after that I will not need you anymore", and the Trigger should not care about the reason, it should just do it's job, and stop after the timeout. WDYT?

@dstandish

Copy link
Copy Markdown
Contributor

I'm open to split them, do you have any suggestion to how we should do that?

If it doesn't seem practical, don't worry about it.

@dstandish

Copy link
Copy Markdown
Contributor

Ok, I put together what is a bit of a simpler and less invasive approach here: #33718

Please take a look when you have a chance.

Essentially, for sensors, if I have it right, we can just use the existing trigger timeout feature to monitor for the sensor timeout, and we can raise AirflowSensorTimeout when that happens.

@hussein-awala hussein-awala added the pinned Protect from Stalebot auto closing label Sep 20, 2023
@eladkal

eladkal commented Jan 12, 2025

Copy link
Copy Markdown
Contributor

suppressed by #33718

@eladkal eladkal closed this Jan 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providers area:Scheduler including HA (high availability) scheduler pinned Protect from Stalebot auto closing provider:microsoft-azure Azure-related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Find a solution to handle the execution timeout in Triggers

4 participants