Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 17.7k
OpenLineage: add execute_in_thread to emit task events without forking#68708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mobuchowski
merged 7 commits into
apache:main
from
mobuchowski:openlineage-execute-in-threadJul 22, 2026
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6c05e75
OpenLineage: add execute_in_thread to emit task events without forking
mobuchowski 562be27
Regenerate OpenLineage get_provider_info.py with execute_in_thread co…
mobuchowski d5adff3
OpenLineage: correct execute_in_thread docs on supervisor-channel beh…
mobuchowski dcfdbdf
OpenLineage: match generated get_provider_info.py option ordering
mobuchowski e7c657a
catch exceptions on thread path
mobuchowski 8fe8b4a
add troubleshooting doc
mobuchowski c298ec1
Merge branch 'main' into openlineage-execute-in-thread
mobuchowski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6 providers/openlineage/src/airflow/providers/openlineage/conf.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7 providers/openlineage/src/airflow/providers/openlineage/get_provider_info.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 52 additions & 1 deletion
53 providers/openlineage/src/airflow/providers/openlineage/plugins/listener.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -19,6 +19,7 @@ | ||
| import logging | ||
| import os | ||
| import sys | ||
| import threading | ||
| from concurrent.futures import ProcessPoolExecutor | ||
| from concurrent.futures.process import BrokenProcessPool | ||
| from datetime import datetime | ||
| @@ -974,10 +975,60 @@ def _on_task_instance_manual_state_change( | ||
| def _execute(self, callable, callable_name: str, use_fork: bool = False): | ||
| if use_fork: | ||
| self._fork_execute(callable, callable_name) | ||
| if conf.execute_in_thread(): | ||
| self._thread_execute(callable, callable_name) | ||
| else: | ||
| self._fork_execute(callable, callable_name) | ||
| else: | ||
| callable() | ||
| def _thread_execute(self, callable, callable_name: str): | ||
| """ | ||
| Run OpenLineage event emission in a time-bounded daemon thread. | ||
| Opt-in alternative to :meth:`_fork_execute`, enabled via | ||
| ``[openlineage] execute_in_thread``. Unlike forking, this never duplicates the | ||
| task runner process, so no supervisor connection is inherited and left in a broken | ||
| state -- emission therefore cannot strand the task in the ``running`` state. The | ||
| task runner waits at most ``[openlineage] execution_timeout`` for emission and then | ||
| proceeds. Metadata extraction still runs in-process with full access to the task | ||
| runtime, so Operators whose extractors resolve Connections, Variables or XComs keep | ||
| working. | ||
| """ | ||
| def _run(): | ||
| try: | ||
| callable() | ||
| except Exception: | ||
| self.log.warning( | ||
| "OpenLineage %s thread failed. This has no impact on actual task execution status.", | ||
| callable_name, | ||
| exc_info=True, | ||
| ) | ||
| thread = threading.Thread( | ||
| target=_run, | ||
| name=f"openlineage-{callable_name}", | ||
| daemon=True, | ||
| ) | ||
| thread.start() | ||
| thread.join(timeout=conf.execution_timeout()) | ||
mobuchowski marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if thread.is_alive(): | ||
| # Emission is still running. We deliberately do not keep waiting: the thread is a | ||
| # daemon, reaped when the process exits. Unlike the fork path -- where parent and | ||
| # child shared a socket fd with no cross-process locking and could interleave bytes | ||
| # on the supervisor channel -- this thread reaches the supervisor only through the | ||
| # shared SUPERVISOR_COMMS threading lock, so it cannot corrupt the protocol. The main | ||
| # thread may briefly wait on that lock if the abandoned thread is mid-request, but the | ||
| # wait is bounded by a single round trip. This mirrors the fork path terminating an | ||
| # over-running child. | ||
| self.log.warning( | ||
| "OpenLineage %s thread did not finish within execution_timeout=%ss and will be " | ||
| "abandoned. This has no impact on actual task execution status.", | ||
| callable_name, | ||
| conf.execution_timeout(), | ||
| ) | ||
| def _terminate_with_wait(self, process: psutil.Process): | ||
| process.terminate() | ||
| try: | ||
98 changes: 98 additions & 0 deletions
98 providers/openlineage/tests/unit/openlineage/plugins/test_listener.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.