From 6fab8b3d62e7e1aeabce5f44f59da151692a7f4e Mon Sep 17 00:00:00 2001 From: necusjz Date: Fri, 9 Oct 2026 16:06:00 +1100 Subject: [PATCH 1/2] fix: intermittent `_DeadlockError` --- .../azure/cli/core/aaz/_poller.py | 4 +++ .../azure/cli/core/tests/test_aaz_poller.py | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py diff --git a/src/azure-cli-core/azure/cli/core/aaz/_poller.py b/src/azure-cli-core/azure/cli/core/aaz/_poller.py index 0e6f3653b56..08066325c4f 100644 --- a/src/azure-cli-core/azure/cli/core/aaz/_poller.py +++ b/src/azure-cli-core/azure/cli/core/aaz/_poller.py @@ -62,6 +62,10 @@ def __init__(self, polling_generator, result_callback): self._done = None self._exception = None + # MSAL lazily imports requests when the credential is created on the polling thread, + # which can raise _DeadlockError (seen on Python 3.14). Import it here on the main thread first. + import requests # pylint: disable=unused-import + self._done = threading.Event() self._thread = threading.Thread( target=with_current_context(self._start), diff --git a/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py b/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py new file mode 100644 index 00000000000..407923ce1f0 --- /dev/null +++ b/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py @@ -0,0 +1,30 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import sys +import unittest +from unittest import mock + +from azure.cli.core.aaz._poller import AAZLROPoller + + +class TestAAZLROPoller(unittest.TestCase): + + def test_requests_imported_before_polling_thread(self): + loaded = [] + + def polling_generator(): + loaded.append("requests" in sys.modules) + yield from () + + with mock.patch.dict(sys.modules): + for name in [n for n in sys.modules if n == "requests" or n.startswith("requests.")]: + del sys.modules[name] + AAZLROPoller(polling_generator=polling_generator(), result_callback=None).wait() + + self.assertEqual(loaded, [True]) + + +if __name__ == "__main__": + unittest.main() From 26b568da6c7f1a359c693d11f927ee10157e4dbf Mon Sep 17 00:00:00 2001 From: necusjz Date: Mon, 12 Oct 2026 11:26:15 +1100 Subject: [PATCH 2/2] chore: check `requests` is loaded at start --- .../azure/cli/core/tests/test_aaz_poller.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py b/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py index 407923ce1f0..a0b244e2dc3 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_aaz_poller.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import sys +import threading import unittest from unittest import mock @@ -13,12 +14,16 @@ class TestAAZLROPoller(unittest.TestCase): def test_requests_imported_before_polling_thread(self): loaded = [] + real_start = threading.Thread.start - def polling_generator(): + def start(thread): loaded.append("requests" in sys.modules) + real_start(thread) + + def polling_generator(): yield from () - with mock.patch.dict(sys.modules): + with mock.patch.dict(sys.modules), mock.patch.object(threading.Thread, "start", start): for name in [n for n in sys.modules if n == "requests" or n.startswith("requests.")]: del sys.modules[name] AAZLROPoller(polling_generator=polling_generator(), result_callback=None).wait()