-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Long story short
aiohttp calls close()
on the transport and then immediately discards it without waiting for connection_lost()
. This is a problem for SSL transports, which have to shut down asynchronously, and it appears aiohttp only "works" with the standard library's SSL transport because of a bug in that transport.
Expected behaviour
aiohttp waiting for connection_lost() before discarding transport
Actual behaviour
It not doing so
Steps to reproduce
import aiohttp
import asyncio
async def main():
conn = aiohttp.TCPConnector(verify_ssl=False)
async with aiohttp.ClientSession(connector=conn) as session:
async with session.get('https://httpbin.org/') as resp:
resp.raise_for_status()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.close()
Run this program and breakpoint connection_made()
and connection_lost()
in client_proto.py. The former is called, but not the latter.
The program appears to run successfully, but to convince you that something is severely amiss, add a __del__
method to the class _SelectorSocketTransport
in asyncio's selector_events.py and breakpoint it and that transport's close()
method. You'll see that close()
is never called and __del__
gets called during shutdown, after the loop has been closed and the module finalised, because it still has a reader registered and is therefore leaked (while still waiting on I/O) in a reference cycle with the event loop.
If I'm understanding it right, the overall behaviour appears to be due to two bugs, one in asyncio and one in aiohttp:
- When
close()
is called on the SSLProtocolTransport in asyncio's ssl_proto.py it initiates an asynchronous shutdown process, which should ultimately result in it callingconnection_lost()
. However, this doesn't progress for reasons I haven't fully figured out, soconnection_lost()
is never called. The shutdown process is clearly broken because it never results inclose()
being called on the underlying transport, which is leaked as shown above. - aiohttp doesn't wait for
connection_lost()
, so the hang that one would expect as a result of this never happens. By luck, the broken shutdown sequence never actually causes any errors.
I'm actually more interested in the latter, the aiohttp behaviour, because I've written my own SSL transport (based on pyopenssl), which does a correct asynchronous shutdown sequence. When aiohttp discards my transport without waiting for connection_lost()
, it causes errors, which I don't think are my fault.
Your environment
Tested on Windows and Linux with python 3.5.1 and 3.5.2 and aiohttp 2.0.7 and 2.1.0.