Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla
, '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 DeadlockImminentError when a connection is resolved inside an async task by dabla · Pull Request #71890 · apache/airflow · GitHub
Skip to content

Fix DeadlockImminentError when a connection is resolved inside an async task - #71890

Open
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri
Open

Fix DeadlockImminentError when a connection is resolved inside an async task#71890
dabla wants to merge 2 commits into
apache:mainfrom
dabla:fix/deadlock-mask-secret-get-uri

Conversation

@dabla

@dabladabla commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a DeadlockImminentError raised in Airflow 3.3.1 when a connection is resolved inside an async task via await BaseHook.aget_hook(...) or await BaseHook.aget_connection(...).

 @task
async def get_relations(updated_id, **context):
hook = await KiotaRequestAdapterHook.aget_hook(conn_id=MSGRAPH_CONN_ID)
return await hook.paginated_run(
url=f"groups/{updated_id[0]}/owners",
query_parameters={"$select": "id"},
)

Problem

The call chain when fetching a connection inside an async task is:

await Connection.async_get() ← async, running on the event-loop thread
→ Connection.get_uri() ← sync, still on event-loop thread
→ self.extra_dejson ← sync property, still on event-loop thread
→ mask_secret()
→ comms.send() ← ERROR: DeadlockImminentError

The error message says "use the async equivalents" — but the caller already is.
The bug is inside Connection itself: get_uri() and extra_dejson have no async counterparts, so secret masking always goes through the blocking comms.send(). amask_secret() (which uses asend()) already existed in airflow.sdk.log
but nothing in Connection called it.

Changes

task-sdk/src/airflow/sdk/definitions/connection.py

  • Extract all URI-assembly logic into a new private _build_uri(self, extra_dejson: dict) helper so sync and async paths share a single implementation.
  • Refactor get_uri() to return self._build_uri(self.extra_dejson) — behaviour is identical to before.
  • Add aextra_dejson() — async method that mirrors extra_dejson but awaits amask_secret() instead of calling the blocking mask_secret().
  • Add aget_uri() — async method that calls await self.aextra_dejson() and delegates to _build_uri().

task-sdk/tests/task_sdk/definitions/test_connection.py

  • test_aget_uriaget_uri() produces the same URI as get_uri() and delegates to aextra_dejson.
  • test_aextra_dejson_calls_amask_secretaextra_dejson() uses amask_secret (async) and never calls the sync mask_secret.
  • test_aextra_dejson_no_extraaextra_dejson() returns {} without calling amask_secret when extra is None.

Usage

# Before — triggers DeadlockImminentError in Airflow 3.3.1:conn=awaitConnection.async_get("my_conn")
uri=conn.get_uri()
# After — safe inside any async task:conn=awaitConnection.async_get("my_conn")
uri=awaitconn.aget_uri()

Was generative AI tooling used to co-author this PR?
  • [ x ] Yes (please specify the tool below)

Claude Sonnet 4.6 (GitHub Copilot)


  • Read the Pull Request Guidelines for more information. Note: commit author/co-author name and email in commits become permanently public when merged.
  • For fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
  • When adding dependency, check compliance with the ASF 3rd Party License Policy.
  • For significant user-facing changes create newsfragment: {pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.

…rror in async tasks
get_uri() accesses extra_dejson, which calls the synchronous mask_secret() →
comms.send() from within the event-loop thread. Any async hook or task that
calls aget_hook() / aget_connection() triggers this path, and Airflow 3.3.1's
DeadlockImminentError detection surfaces the bug.
Add aextra_dejson() — an async method that awaits amask_secret() instead of
the blocking mask_secret() — and aget_uri(), which delegates URI assembly to
a new shared _build_uri() helper and calls await self.aextra_dejson(). This
keeps the entire connection-serialisation path safely on the async stack.
The sync get_uri() / extra_dejson are unchanged; _build_uri() is the single
source of truth for the URI format, shared by both paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dabla
dablaforce-pushed the fix/deadlock-mask-secret-get-uri branch from db8a1c7 to 9663c6aCompareAugust 20, 2026 19:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeadlockImminentError is raised when a connection is resolved inside an async task

1 participant

@dabla