Repository: aio-libs/aiohttp

Issue:
"application data after close notify on" 4.0.0a1
See: https://github.com/aio-libs/aiohttp/issues/3477

I'm experiencing this issue on the 4.0.0a1 release.

It can be reproduced with this script (Thanks @Cadair):
```python
import asyncio
import aiohttp

print(aiohttp.__version__)

url = "https://github.com/sunpy/sample-data/blob/master/sunpy/v1/AIA20110607_063303_0335_lowres.fits?raw=true"


async def run_example():
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            print(resp)

asyncio.run(run_example())
```

Python version: 3.7.3, on Linux (WSL).


Discussion:
What OpenSSL version do you use?
It happens for me on two systems at least:
- qemu linux 5.3.0-26:   openssl  1.1.1c  28 May 2019
- WSL: OpenSSL 1.1.0g  2 Nov 2017 (Library: OpenSSL 1.1.1  11 Sep 2018)
Did you try uvloop?
Not yet, I'll do it ASAP and let you know.
So, it does happen for uvloop as well, but the traceback is slightly different:

default loop:
```
Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/sslproto.py", line 530, in data_received
    ssldata, appdata = self._sslpipe.feed_ssldata(data)
  File "/usr/lib/python3.7/asyncio/sslproto.py", line 207, in feed_ssldata
    self._sslobj.unwrap()
  File "/usr/lib/python3.7/ssl.py", line 778, in unwrap
    return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2629)
```
uvloop:
```
Traceback (most recent call last):
  File "uvloop/sslproto.pyx", line 571, in uvloop.loop.SSLProtocol._do_shutdown
  File "/usr/lib/python3.7/ssl.py", line 778, in unwrap
    return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2629)
```

The error disappears if I actually read the content and wait for it... (Add ``await resp.content.read()`` to the above snippet.)
```python
async def run_example():
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            await resp.content.read()
```

(edit: the above tracebacks are from Python 3.7.5, aiohttp 4.0.0a1, qemu VM, Linux 5.3.0-26-generic Ubuntu 19.10)
There is this warning which I get that can be helpful (here with tracemalloc):
```
/home/jonathan/.virtualenvs/candela/lib/python3.7/site-packages/aiohttp/client.py:518: RuntimeWarning: coroutine 'noop' was never awaited
  resp.release()
Object allocated at (most recent call last):
  File "/home/jonathan/.virtualenvs/candela/lib/python3.7/site-packages/aiohttp/client_reqrep.py", lineno 869
    return noop()
/home/jonathan/.virtualenvs/candela/lib/python3.7/site-packages/aiohttp/client.py:541: RuntimeWarning: coroutine 'noop' was never awaited
  resp.release()
Object allocated at (most recent call last):
  File "/home/jonathan/.virtualenvs/candela/lib/python3.7/site-packages/aiohttp/client_reqrep.py", lineno 869
    return noop()
/home/jonathan/.virtualenvs/candela/lib/python3.7/site-packages/aiohttp/client.py:977: RuntimeWarning: coroutine 'noop' was never awaited
  self._resp.release()
Object allocated at (most recent call last):
  File "/home/jonathan/.virtualenvs/candela/lib/python3.7/site-packages/aiohttp/client_reqrep.py", lineno 877
    return noop()
```
Thanks!
A little update on this. I've updated Python to 3.8.2 and it's still the case.
It does not happen with httpx or requests.

Looks like it's really aiohttp==4.0.0a1. It does not happen with aiohttp==3.6.2. Very reliable to reproduce here with the above snippet.
I'm not sure if it happens on the master branch, I don't manage to build aiohttp right now.

@asvetlov : Any idea on when there will be a stable 4.0 release, or whether I better downgrade for now?
I am experiencing the same issue with Python 3.9 and 4.0.0a1. Never happened with aiohttp 3.6 and 3.7. Using uvloop.
Any updates on this matter?
That version is well over a year old. I'd recommend either using master on sticking with the 3.x releases for now.
Although I can reproduce on master with the original example (though the error disappears if I actually read the response, like `await resp.read()`).

Resolving patch:
diff --git a/aiohttp/connector.py b/aiohttp/connector.py
--- a/aiohttp/connector.py
+++ b/aiohttp/connector.py
@@ -45,7 +45,7 @@
 )
 from .client_proto import ResponseHandler
 from .client_reqrep import SSL_ALLOWED_TYPES, ClientRequest, Fingerprint
-from .helpers import _SENTINEL, ceil_timeout, is_ip_address, sentinel
+from .helpers import _SENTINEL, ceil_timeout, is_ip_address, sentinel, set_result
 from .http import RESPONSES
 from .locks import EventResultOrError
 from .resolver import DefaultResolver
@@ -628,6 +628,9 @@ def _release(
         if should_close or protocol.should_close:
             transport = protocol.transport
             protocol.close()
+            # TODO: Remove once fixed: https://bugs.python.org/issue39951
+            # See PR #6321
+            set_result(protocol.closed, None)
 
             if key.is_ssl and not self._cleanup_closed_disabled:
                 self._cleanup_closed_transports.append(transport)


Test patch:
diff --git a/tests/test_connector.py b/tests/test_connector.py
--- a/tests/test_connector.py
+++ b/tests/test_connector.py
@@ -508,6 +508,14 @@ async def test_release_close(key: Any) -> None:
     assert proto.close.called
 
 
+async def test_release_proto_closed_future(loop: Any, key: Any):
+    conn = aiohttp.BaseConnector()
+    protocol = mock.Mock(should_close=True, closed=loop.create_future())
+    conn._release(key, protocol)
+    # See PR #6321
+    assert protocol.closed.result() is None
+
+
 async def test__drop_acquire_per_host1(loop: Any) -> None:
     conn = aiohttp.BaseConnector()
     conn._drop_acquired_per_host(123, 456)