From 443aa5d2a548d30996a953b649e4059a04628b55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:22:39 +0000 Subject: [PATCH] Fix integration test worker crashes on Python 3.13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to prevent pytest-xdist workers from crashing during Azure Functions integration tests: 1. Add `start_new_session=True` to subprocess on Linux so signals (e.g. from test-timeout) cannot propagate between the func host and the xdist worker process. 2. Add an overall 100-second budget to the fixture setup loop so the retry logic never exceeds the 120-second test timeout. When pytest-timeout's thread method fires during fixture setup and the thread doesn't respond, it calls os._exit() which kills the xdist worker – this is the root cause of the "Not properly terminated" crashes. 3. Remove the `UV_PYTHON: "3.10"` workaround from both workflow files so integration tests actually run on Python 3.13. Co-authored-by: larohra <41490930+larohra@users.noreply.github.com> --- .../workflows/python-integration-tests.yml | 1 - .github/workflows/python-merge-tests.yml | 1 - .../tests/integration_tests/conftest.py | 31 ++++++++++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-integration-tests.yml b/.github/workflows/python-integration-tests.yml index 56525b442e..ad9258c20f 100644 --- a/.github/workflows/python-integration-tests.yml +++ b/.github/workflows/python-integration-tests.yml @@ -170,7 +170,6 @@ jobs: environment: integration timeout-minutes: 60 env: - UV_PYTHON: "3.10" OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }} OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }} OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }} diff --git a/.github/workflows/python-merge-tests.yml b/.github/workflows/python-merge-tests.yml index 6d169948db..1ad170a6b0 100644 --- a/.github/workflows/python-merge-tests.yml +++ b/.github/workflows/python-merge-tests.yml @@ -285,7 +285,6 @@ jobs: runs-on: ubuntu-latest environment: integration env: - UV_PYTHON: "3.10" OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }} OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }} OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }} diff --git a/python/packages/azurefunctions/tests/integration_tests/conftest.py b/python/packages/azurefunctions/tests/integration_tests/conftest.py index 3f6060d93d..c3ee433534 100644 --- a/python/packages/azurefunctions/tests/integration_tests/conftest.py +++ b/python/packages/azurefunctions/tests/integration_tests/conftest.py @@ -371,8 +371,15 @@ def _start_function_app(sample_path: Path, port: int) -> subprocess.Popen[Any]: shell=True, env=env, ) - # On Unix, don't use shell=True to avoid shell wrapper issues - return subprocess.Popen(["func", "start", "--port", str(port)], cwd=str(sample_path), env=env) + # On Unix, use start_new_session=True to isolate the process group from the + # pytest-xdist worker. Without this, signals (e.g. from test-timeout) can + # propagate to the func host and vice-versa, potentially killing the worker. + return subprocess.Popen( + ["func", "start", "--port", str(port)], + cwd=str(sample_path), + env=env, + start_new_session=True, + ) def _wait_for_function_app_ready(func_process: subprocess.Popen[Any], port: int, max_wait: int = 60) -> None: @@ -529,18 +536,33 @@ def function_app_for_test(request: pytest.FixtureRequest) -> Iterator[dict[str, _load_and_validate_env() max_attempts = 3 + # The overall budget MUST be shorter than the pytest-timeout value + # (--timeout=120 by default) so that the fixture finishes cleanly instead + # of being killed by os._exit() which crashes the xdist worker. + overall_budget = 100 # seconds – leaves headroom below the 120 s test timeout last_error: Exception | None = None func_process: subprocess.Popen[Any] | None = None base_url = "" port = 0 + overall_start = time.time() + attempts_made = 0 for _ in range(max_attempts): + remaining = overall_budget - (time.time() - overall_start) + if remaining < 10: + # Not enough time for another attempt; bail out. + break + + attempts_made += 1 port = _find_available_port() base_url = _build_base_url(port) func_process = _start_function_app(sample_path, port) try: - _wait_for_function_app_ready(func_process, port) + # Cap each attempt's wait to the remaining budget minus a small + # buffer for cleanup. + per_attempt_wait = min(60, int(remaining) - 5) + _wait_for_function_app_ready(func_process, port, max_wait=max(per_attempt_wait, 10)) last_error = None break except FunctionAppStartupError as exc: @@ -549,7 +571,8 @@ def function_app_for_test(request: pytest.FixtureRequest) -> Iterator[dict[str, func_process = None if func_process is None: - error_message = f"Function app failed to start after {max_attempts} attempt(s)." + elapsed = int(time.time() - overall_start) + error_message = f"Function app failed to start after {attempts_made} attempt(s) ({elapsed}s elapsed)." if last_error is not None: error_message += f" Last error: {last_error}" pytest.fail(error_message)