Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Fix EMR container job not cancelled on deferral timeout by aurangzaib048 · Pull Request #64770 · apache/airflow · GitHub
Skip to content

Fix EMR container job not cancelled on deferral timeout - #64770

Merged
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup
May 7, 2026
Merged

Fix EMR container job not cancelled on deferral timeout#64770
vincbeck merged 18 commits into
apache:mainfrom
aurangzaib048:fix/emr-container-trigger-cleanup

Conversation

@aurangzaib048

@aurangzaib048aurangzaib048 commented Apr 6, 2026

Copy link
Copy Markdown

When EmrContainerOperator runs in deferrable mode and the trigger times out or the task is killed, the EMR job keeps running on the cluster. This leads to orphaned jobs consuming resources and duplicate executions on retry.

This PR adds cancel-on-kill support to EmrContainerTrigger following the same proven pattern as EMrServerlessStartJobTrigger (PR #51883):

  • Override run() in EmrContainerTrigger to catch asyncio.CancelledError and cancel the EMR job via hook.stop_query() when safe to do so
  • Add safe_to_cancel() check to distinguish user-initiated kills from triggerer restarts (avoids cancelling jobs during triggerer restart)
  • Add cancel_on_kill parameter (default True) for opt-out
  • Update EmrContainerOperator.execute_complete() to cancel the job when the trigger reports a failure/timeout event
  • All cancellation paths are wrapped in try/except to ensure proper error propagation (CancelledError is always re-raised, original AirflowException is preserved)

closes: #60517


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

@boring-cyborg

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide (https://github.com/apache/airflow/blob/main/contributing-docs/README.rst)
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@boring-cyborgboring-cyborgBot added area:providers provider:amazon AWS/Amazon - related issues labels Apr 6, 2026
@kaxil
kaxil requested a review from CopilotApril 10, 2026 19:55

CopilotAI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes orphaned EMR on EKS container jobs when deferrable tasks time out or are killed by adding cancel-on-cancel semantics to the trigger and ensuring the operator cancels the job on terminal error events.

Changes:

  • Add cancel_on_kill support to EmrContainerTrigger, including a safe_to_cancel() check and explicit CancelledError handling to stop the remote job.
  • Update EmrContainerOperator.execute_complete() to stop the EMR job when the trigger reports failure/timeout.
  • Add/extend unit tests to cover trigger serialization and cancellation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

FileDescription
providers/amazon/src/airflow/providers/amazon/aws/triggers/emr.pyAdds cancel-on-kill behavior and safe cancellation checks in EmrContainerTrigger.
providers/amazon/src/airflow/providers/amazon/aws/operators/emr.pyCancels EMR container jobs in execute_complete() on non-success events; passes cancel_on_kill=True to trigger.
providers/amazon/tests/unit/amazon/aws/triggers/test_emr.pyAdds serialization and cancellation-path unit tests for EmrContainerTrigger.
providers/amazon/tests/unit/amazon/aws/operators/test_emr_containers.pyAdds unit tests verifying job cancellation on execute_complete() failure.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/operators/test_emr_containers.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
Comment threadproviders/amazon/tests/unit/amazon/aws/triggers/test_emr.py Outdated

CopilotAI left a comment

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@eladkal
eladkal requested a review from vincbeckApril 11, 2026 13:27

@vincbeckvincbeck left a comment

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 understand the goal here but I am also wondering if it is worth it. You're adding a lot of code just for that use case, which creates significant complexity in the operator. By any chance, is there any way to make it simpler?

Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/operators/emr.py Outdated
Comment threadproviders/amazon/src/airflow/providers/amazon/aws/triggers/emr.py Outdated
@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 Thanks for working through all the review feedback — every thread on the PR is resolved. This PR has a small issue that needs to be addressed before it can be reviewed — please see our Pull Request quality criteria.

Issues found:

  • Pre-commit / static checks: CI image checks / Static checks is failing. Run prek run --from-ref main --stage pre-commit locally, fix whatever it flags, and push.

What to do next:

  • Make sure static checks pass locally: prek run --from-ref main --stage pre-commit.
  • Once CI goes green, the PR is ready for re-review.

Leaving the PR open (not converting to draft) since you have clearly worked through the earlier reviewer feedback — all 20 review threads are resolved. Just this one CI fix left. There is no rush — take your time.

- Wrap stop_query() in try/except inside CancelledError handler to
ensure CancelledError is always re-raised even if cancellation fails
- Wrap safe_to_cancel() call to prevent DB/API errors from replacing
the CancelledError
- Wrap stop_query() in execute_complete() to preserve original error
- Narrow exception handler in run() from Exception to AirflowException
to match base class behavior and avoid swallowing programming bugs
- Narrow except in get_task_state() to (KeyError, TypeError) with
proper exception chaining via 'from e'
- Use "error" status in failure TriggerEvent to match base class
convention and include return_key/return_value
The base class hook() method is typed to return AwsGenericHook[Any],
which does not expose the stop_query() method specific to
EmrContainerHook. Add an explicit type annotation with a type ignore
to satisfy mypy.
- Add except Exception fallback in trigger run() to match base class
behavior for non-AirflowException errors
- Wrap sync get_task_instance() and stop_query() with sync_to_async to
avoid blocking the triggerer event loop
- Add cancel_on_kill parameter to EmrContainerOperator for user opt-out
- Add test verifying original error is preserved when stop_query fails
…omplete
- Pass virtual_cluster_id to EmrContainerHook in hook() so stop_query()
can call cancel_job_run with the correct cluster ID
- Use validated_event.get("job_id") in execute_complete() as primary
source since self.job_id may be None after deferral reconstruction
- Move 'from sqlalchemy import select' to module-level conditional
block to avoid repeated function-scope imports
- Add spec=EmrContainerHook to MagicMock instances in trigger tests
to catch attribute/typo bugs
- Merge except AirflowException and except Exception into single
except Exception handler since both yield identical error events
- Remove test_serialization_includes_cancel_on_kill since the existing
test_serialization already covers cancel_on_kill=True default
- Remove unused AirflowException import from triggers/emr.py
The execution API uses task_id as key for non-mapped tasks but
"{task_id}_{map_index}" for mapped tasks (map_index >= 0). Use the
correct key format so safe_to_cancel works for mapped deferrable
EMR container jobs.
The worker running execute_complete may differ from the one that ran
execute, so self.job_id cannot be relied upon after deferral.
Only attempt job cancellation in execute_complete when cancel_on_kill
is True, consistent with the trigger-side behavior.
Previously execute_complete cancelled on any non-success status, which
would silently swallow future trigger statuses into the cancel path.
Now only the explicit 'error' status triggers cancellation.
@aurangzaib048
aurangzaib048force-pushed the fix/emr-container-trigger-cleanup branch from 88172f4 to fe1e1fcCompareApril 23, 2026 07:40
@vincbeck

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

@o-nikolas

Copy link
Copy Markdown
Contributor

Something we might want to leverage? #65590

Yes, let's definitely make use of this if we can here!

@potiuk

Copy link
Copy Markdown
Member

@aurangzaib048 — Your unresolved review thread(s) from @o-nikolas, @vincbeck appear to have been addressed (post-review commits and/or in-thread replies on every thread, with the latest commit pushed after the most recent thread). I've added the ready for maintainer review label so the PR re-enters the maintainer review queue.

@o-nikolas, @vincbeck — could you take another look when you have a chance? If you agree the feedback was addressed, please mark the threads as resolved so the queue signal stays accurate. If a thread still needs work, please reply in-line — @aurangzaib048 will follow up.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.

@potiukpotiuk added the ready for maintainer review Set after triaging when all criteria pass. label May 5, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

Still waiting this comment to be addressed: https://github.com/apache/airflow/pull/64770/changes#r3124747034

Replaces the custom run() override and safe_to_cancel/get_task_state/
get_task_instance helpers with the new BaseTrigger.on_kill() framework
hook. The framework now distinguishes user-initiated cancellation from
triggerer shutdown, so the provider no longer needs to query task state
to decide whether to cancel the EMR container job.
execute_complete() is reduced to a simple result router; cancellation
on user kill is owned solely by the trigger's on_kill().
@aurangzaib048

Copy link
Copy Markdown
Author

Adopted the BaseTrigger.on_kill() pattern from #65590, this removed safe_to_cancel / get_task_state / get_task_instance from EmrContainerTrigger entirely.

@vincbeck
vincbeck merged commit 02f9f6b into apache:mainMay 7, 2026
95 checks passed
@boring-cyborg

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providersprovider:amazonAWS/Amazon - related issuesready for maintainer reviewSet after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triggerer timeout exception is not handled properly

5 participants

@aurangzaib048@potiuk@vincbeck@o-nikolas