Skip to content

TEST: Release Windows event-loop wakeup ports on close - #3361

Queued
Richard Lundeen (richlundeen) wants to merge 2 commits into
microsoft:mainfrom
richlundeen:richlundeen-dataset-refresh-dequeue
Queued

Richard Lundeen (richlundeen) wants to merge 2 commits into
microsoft:mainfrom
richlundeen:richlundeen-dataset-refresh-dequeue

Conversation

@richlundeen

@richlundeen Richard Lundeen (richlundeen) commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Description

The Windows Python 3.13 unit-test job removed #3206 from the merge queue. Job 118347021912 reported WinError 10055 while setting up tests/unit/backend/test_runtime_lifecycle.py::test_saved_opt_in_is_required. The test body did not run. The stack was pytest-asyncio runner creation -> native Proactor event loop -> private TCP socket pair -> connect(). The other 29,821 tests passed.

Windows implements the loop's private wakeup socket pair with TCP. Normal loop closure leaves connections in TIME_WAIT; thousands of function-scoped loops can put pressure on the ephemeral port pool. A bounded local Windows experiment left 28 TCP entries after 32 native loop closures, but zero after abortive closure of only the two private wakeup sockets. The new 64-loop regression fails against the original asyncio.new_event_loop factory and passes with this fix.

Use one pytest-asyncio factory fixture on Windows to keep native, function-scoped Proactor loops and release only their private wakeup sockets on close. Application sockets keep their normal close behavior. The native close path still runs if socket-option setup fails, and the error still propagates. Cancellation, thread wakeups, subprocess support, and refusal to close a running loop remain covered. Other platforms keep their existing loop setup.

The review update addresses both of Roman's findings: require pytest-asyncio 1.4 or later and apply the factory to synchronous tests with async fixtures as well as coroutine tests. The public 1.4 hook misses fixture-only runners, so the suite overrides the internal _asyncio_loop_factory fixture instead. Explicit wiring checks cover both paths and expose incompatible plugin changes. The lockfile changes only the minimum-version metadata; locked package versions and registry URLs are unchanged.

Thread wakeup and cleanup waits are bounded. An independent thread-based watchdog also stops a stalled loop. The subprocess checks kill and reap an unfinished child in finally, with deterministic timeout and cancellation coverage.

This changes test infrastructure only. It does not change production code, share loops, add retries, or alter dataset refresh behavior. Test watchdogs use the existing shared deadlock backstop, not short latency assertions.

A later failure in the same merge group was the seeded Operations 390px E2E test. Open PR #3356 adds startup and preference synchronization to that same test flow; this PR does not duplicate its frontend changes.

Tests and Documentation

Windows, Python 3.14.2, pytest-asyncio 1.4.0:

  • uv sync --frozen --extra all - passed.
  • uv run --no-sync pytest tests\unit\test_event_loop.py tests\unit\prompt_target\test_text_target.py tests\unit\backend\test_runtime_lifecycle.py -q -W error::pytest.PytestUnraisableExceptionWarning - passed: 86 tests.
  • uv run --no-sync ty check test_support\event_loop.py tests\unit\conftest.py tests\unit\test_event_loop.py - passed.
  • $env:UV_NO_SYNC='1'; make unit-test - passed: 29,690 tests, 50 skipped, in 16 minutes 3 seconds.
  • $env:UV_NO_SYNC='1'; uv run --no-sync pre-commit run --all-files - passed: all hooks.
  • uv lock --check --default-index https://pypi.org/simple - passed against the lockfile's existing registry. UV_DEFAULT_INDEX was not changed.

Session-only negative controls confirmed that substituting the original factory fails both runner-wiring assertions. A separate pytest process with thread-safe wakeups disabled exited through the independent watchdog after a controlled two-second limit rather than hanging. These diagnostic injections are not part of the committed suite.

Updated the unit-test contributor guide with factory coverage and the private plugin-fixture contract. No notebooks changed; JupyText and live-provider tests were not run.

Keep function-scoped Proactor loops while avoiding TCP TIME_WAIT buildup from private wakeup sockets. Cover port release, cleanup failures, cancellation, thread wakeups, and subprocess support.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a3cf835d-626c-4bae-94c2-09317acbf654
Comment thread tests/unit/conftest.py Outdated
Comment thread tests/unit/conftest.py Outdated
Use a single pytest-asyncio factory fixture for coroutine and fixture-only runners, require plugin 1.4, and check actual runner wiring. Bound thread wakeups and subprocess lifecycle checks, and verify child cleanup on deterministic timeout and cancellation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a3cf835d-626c-4bae-94c2-09317acbf654
@richlundeen
Richard Lundeen (richlundeen) added this pull request to the merge queue Oct 21, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants