Skip to content

gh-155027: Make test_asyncio's socket harness able to fail a test - #155028

Open
matthiasgoergens wants to merge 2 commits into
python:mainfrom
matthiasgoergens:matthias/fix-asyncio-abort-socket-test
Open

gh-155027: Make test_asyncio's socket harness able to fail a test#155028
matthiasgoergens wants to merge 2 commits into
python:mainfrom
matthiasgoergens:matthias/fix-asyncio-abort-socket-test

Conversation

@matthiasgoergens

@matthiasgoergens matthiasgoergens commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

_abort_socket_test() called self.fail() from the client or server thread. self.fail() raises AssertionError in the calling thread, and both call sites are inside a Thread.run(), so the exception escapes the thread without ever reaching TestCase.run() — the test still reports ok. This patch records the exception instead and re-raises it from tearDown(), on the main thread, where unittest can see it.

It also replaces self.loop.stop() with self.loop.call_soon_threadsafe(self.loop.stop). Event loops are not thread-safe and this call was being made from a non-main thread.

Background, measurements and the history are in #155027. In short: this has never worked in any release since 3.7, and since 3.10 the only thing that notices is regrtest's threading excepthook, which wins the race roughly half the time.

Verification

Same binary, the patch toggled on and off, running a test whose server prog raises ConnectionResetError — the shape of the Windows CI failure that prompted this:

exit status regrtest verdict
stock main 0 SUCCESS (recorded only as "env changed")
with this patch 2 FAILURE — 1 test failed

The test module used is not included here; see "open question" below.

Please treat this as potentially disruptive

I would rather flag this clearly than have it discovered on the buildbots.

The diff is small but its effect is not local. These failures currently do not fail tests, so making the abort work means any latent failure in the client or server half of a socket test will start failing. The mixin is used by test_sslproto, test_ssl, test_events, test_server, test_streams, test_buffered_proto, test_sock_lowlevel and test_unix_events.

What I can say from here: test_asyncio is green on Linux with the patch, 2,819 tests, five consecutive runs, no flakes.

What I cannot say: anything about the other platforms, and that is where the risk actually is. The failure that prompted this was Windows-only, and I expect it to go red. Platform-specific socket behaviour is precisely what a Linux dev box cannot tell you about, and eight years of accumulated silence is a lot of surface to uncover at once.

So I am not assuming this should merge as-is. Reasonable alternatives, if maintainers prefer: land it early in a release cycle rather than near a beta; or land the diagnosis first, see what turns red across the buildbot fleet, fix those, and enable the abort afterwards. I am happy to split it that way, or to drop it if the churn is not judged worth the benefit.

Open question for reviewers

I have not added a regression test. A natural one would assert that a failing server prog fails the test, but it would have to do so by asserting that a test fails, which needs care to write well in this suite. Happy to add one in whatever form is preferred — or to leave it, given that the change is itself test-infrastructure.

_abort_socket_test() called self.fail() from the client/server thread.
The resulting AssertionError escapes Thread.run() without ever reaching
TestCase.run(), so an error in the server half of a socket test did not
fail it. Record the exception instead and re-raise it from tearDown().

Also stop the event loop with call_soon_threadsafe() rather than calling
loop.stop() directly from a non-main thread.
@matthiasgoergens

Copy link
Copy Markdown
Contributor Author

CI has produced the failure I flagged in the description, and it is worth
recording precisely because it is the change working rather than breaking.

Windows / Build and test (Win32, switch-case)
(run):

======================================================================
ERROR: test_shutdown_corrupted_ssl_sends_close_notify
       (test.test_asyncio.test_sslproto.SelectorStartTLSTests....)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "Lib\threading.py", line 1218, in _bootstrap_inner
  File "Lib\test\test_asyncio\functional.py", line 240, in run
  File "Lib\test\test_asyncio\functional.py", line 268, in _run
  File "Lib\test\test_asyncio\functional.py", line 277, in _handle_client
  File "Lib\test\test_asyncio\test_sslproto.py", line 920, in server
    sock.unwrap()
  File "Lib\ssl.py", line 1402, in unwrap
    s = self._sslobj.shutdown()
ConnectionResetError: [WinError 10054] An existing connection was forcibly
closed by the remote host

That is the same error, in the same test, that #155027 documents as being
silently discarded today. On main this run reports SUCCESS; with this
patch it reports FAILURE. Nothing new is broken — a real failure that
existed before has stopped being invisible.

It also settles a question the issue left open. I had only a single
observation of this ConnectionResetError, so I could not say whether it
was a one-off; it has now recurred on an unrelated run, so the underlying
Windows problem is reproducible rather than incidental.

What I think should happen next

The test asserts that a peer shutting down a corrupted SSL connection sees
a clean TLS EOF rather than a connection reset (gh-98078), and its server
half catches only ssl.SSLError:

    sock.unwrap()
except ssl.SSLError as exc:
    server_err = exc
...
self.assertIsNone(server_err)

A ConnectionResetError is arguably the very condition under test, so
assertIsNone(server_err) passing on it looks like a second bug rather
than something to paper over. But I do not know Windows' TLS shutdown
behaviour well enough to say whether the reset is legitimate there — it may
be that WinError 10054 is expected on this platform and the test needs to
accept it, or it may be that asyncio really is failing to send the
close-notify.

I would rather not guess in a way that turns a real signal green, so I have
left it failing and would welcome a steer. Three options as I see them,
happy to implement whichever is preferred:

  1. Land this as-is and fix the Windows behaviour (or the test's
    expectation) separately, accepting a red Win32 job in between.
  2. Fix the test_sslproto server half in this PR so the suite is green on
    landing.
  3. Hold this until the Windows question is settled, so that turning the
    abort path on never lands a red run.

For what it is worth on the other platforms: test_asyncio is green on
Linux with this patch, 2819 tests over five consecutive runs, no flakes.

When SSL shutdown failed, SSLProtocol copied close_notify to the raw
transport and immediately force-closed it. Selector transports discarded
buffered output and proactor transports cancelled the active write.

Close the raw transport normally after SSL write flow resumes, keep the
shutdown timeout active while it drains, and preserve the SSL exception for
the application protocol unless the raw transport reports its own error.
Sign up for free to 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.

1 participant