Skip to content

Commit dd175b6

Browse files
Fix regression with connection upgrade (#7879) (#7908)
Fixes #7867. (cherry picked from commit 48b1558)
1 parent 946523d commit dd175b6

File tree

4 files changed

+32
-11
lines changed

4 files changed

+32
-11
lines changed

CHANGES/7879.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a regression where connection may get closed during upgrade. -- by :user:`Dreamsorcerer`

aiohttp/client_reqrep.py

+8-11
Original file line numberDiff line numberDiff line change
@@ -1006,19 +1006,14 @@ def _response_eof(self) -> None:
10061006
if self._closed:
10071007
return
10081008

1009-
if self._connection is not None:
1010-
# websocket, protocol could be None because
1011-
# connection could be detached
1012-
if (
1013-
self._connection.protocol is not None
1014-
and self._connection.protocol.upgraded
1015-
):
1016-
return
1017-
1018-
self._release_connection()
1009+
# protocol could be None because connection could be detached
1010+
protocol = self._connection and self._connection.protocol
1011+
if protocol is not None and protocol.upgraded:
1012+
return
10191013

10201014
self._closed = True
10211015
self._cleanup_writer()
1016+
self._release_connection()
10221017

10231018
@property
10241019
def closed(self) -> bool:
@@ -1113,7 +1108,9 @@ async def read(self) -> bytes:
11131108
elif self._released: # Response explicitly released
11141109
raise ClientConnectionError("Connection closed")
11151110

1116-
await self._wait_released() # Underlying connection released
1111+
protocol = self._connection and self._connection.protocol
1112+
if protocol is None or not protocol.upgraded:
1113+
await self._wait_released() # Underlying connection released
11171114
return self._body # type: ignore[no-any-return]
11181115

11191116
def get_encoding(self) -> str:

aiohttp/connector.py

+4
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ def __del__(self, _warnings: Any = warnings) -> None:
127127
context["source_traceback"] = self._source_traceback
128128
self._loop.call_exception_handler(context)
129129

130+
def __bool__(self) -> Literal[True]:
131+
"""Force subclasses to not be falsy, to make checks simpler."""
132+
return True
133+
130134
@property
131135
def loop(self) -> asyncio.AbstractEventLoop:
132136
warnings.warn(

tests/test_client_functional.py

+19
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,25 @@ async def handler(request):
173173
assert 1 == len(client._session.connector._conns)
174174

175175

176+
async def test_upgrade_connection_not_released_after_read(aiohttp_client) -> None:
177+
async def handler(request: web.Request) -> web.Response:
178+
body = await request.read()
179+
assert b"" == body
180+
return web.Response(
181+
status=101, headers={"Connection": "Upgrade", "Upgrade": "tcp"}
182+
)
183+
184+
app = web.Application()
185+
app.router.add_route("GET", "/", handler)
186+
187+
client = await aiohttp_client(app)
188+
189+
resp = await client.get("/")
190+
await resp.read()
191+
assert resp.connection is not None
192+
assert not resp.closed
193+
194+
176195
async def test_keepalive_server_force_close_connection(aiohttp_client) -> None:
177196
async def handler(request):
178197
body = await request.read()

0 commit comments

Comments
 (0)