Skip to content

asyncio.sslproto.SSLProtocol exception handling triggers TypeError: SSLProtocol._abort() takes 1 positional argument but 2 were given #113214

@mjpieters

Description

@mjpieters

Bug report

Bug description:

There are several pathways to this bug, all which call asyncio.sslproto._SSLProtocolTransport._force_close() with an exception instance:

  • During shutdown, if the flushing state takes too long (asyncio.sslproto.SSLProtocolTransport._check_shutdown_timeout() is called)
  • Anything that triggers a call to asyncio.sslproto.SSLProtocol._fatal_error(), e.g. SSL handshake timeout or exception, SSL shutdown timeout or exception, an exception during reading, exception raised in the app transport EOF handler, etc.

I'm seeing this when using a HTTPS proxy with a aiohttp client session (which wraps TLS in TLS), but I don't think it is specific to that context. I'm seeing these tracebacks:

Fatal error on SSL protocol protocol: <asyncio.sslproto.SSLProtocol object at 0x7fe36f3a1350> transport: <_SelectorSocketTransport closing fd=6 read=idle write=<idle, bufsize=0>> Traceback (most recent call last): File ".../lib/python3.11/asyncio/sslproto.py", line 644, in _do_shutdown self._sslobj.unwrap() File ".../lib/python3.11/ssl.py", line 983, in unwrap return self._sslobj.shutdown() ^^^^^^^^^^^^^^^^^^^^^^^ ssl.SSLError: [SSL: APPLICATION_DATA_AFTER_CLOSE_NOTIFY] application data after close notify (_ssl.c:2702) During handling of the above exception, another exception occurred: Traceback (most recent call last): File ".../lib/python3.11/asyncio/sslproto.py", line 731, in _do_read self._do_read__buffered() File ".../lib/python3.11/asyncio/sslproto.py", line 765, in _do_read__buffered self._app_protocol_buffer_updated(offset) File ".../lib/python3.11/asyncio/sslproto.py", line 445, in buffer_updated self._do_shutdown() File ".../lib/python3.11/asyncio/sslproto.py", line 648, in _do_shutdown self._on_shutdown_complete(exc) File ".../lib/python3.11/asyncio/sslproto.py", line 660, in _on_shutdown_complete self._fatal_error(shutdown_exc) File ".../lib/python3.11/asyncio/sslproto.py", line 911, in _fatal_error self._transport._force_close(exc) File ".../lib/python3.11/asyncio/sslproto.py", line 252, in _force_close self._ssl_protocol._abort(exc) TypeError: SSLProtocol._abort() takes 1 positional argument but 2 were given 

To me, the implementation of _SSLProtocolTransport._force_close() looks like an unfinished copy of the _SSLProtocolTransport.abort() method:

def abort(self):
"""Close the transport immediately.
Buffered data will be lost. No more data will be received.
The protocol's connection_lost() method will (eventually) be
called with None as its argument.
"""
self._closed = True
if self._ssl_protocol is not None:
self._ssl_protocol._abort()
def _force_close(self, exc):
self._closed = True
self._ssl_protocol._abort(exc)

At any rate, the self._ssl_protocol attribute is an instance of SSLProtocol in the same module, and the _abort() method on that class doesn't accept an exception instance:

def _abort(self):
self._set_state(SSLProtocolState.UNWRAPPED)
if self._transport is not None:
self._transport.abort()

I find the test suite surrounding the SSL protocol to be dense enough that I can't easily spot how to provide an update there to reproduce this issue more easily, but the fix looks simple enough: don't pass an argument to _abort().

CPython versions tested on:

3.11, 3.12, CPython main branch

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions