Skip to content

Commit

Permalink
[3.7] #4587 Always check transport is not closing before reuse (#4778) (
Browse files Browse the repository at this point in the history
#5056)

Backports the following commits to 3.7:
 - #4587 Always check transport is not closing before reuse (#4778)

Co-authored-by: Dahuage <Dahuage@users.noreply.github.com>
  • Loading branch information
github-actions[bot] and Dahuage authored Oct 16, 2020
1 parent cf84c9b commit e80df69
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 1 deletion.
10 changes: 10 additions & 0 deletions CHANGES/4587.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Always make sure transport is not closing before reuse a connection.

Reuse a protocol based on keepalive in headers is unreliable.
For example, uWSGI will not support keepalive even it serves a
http1.1 request, except explicitly configure uWSGI with a
`--http-keepalive` option.

Servers designed like uWSGI could cause aiohttp intermittently
raise a ConnectionResetException when the protocol poll runs
out and some protocol is reused.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ Kirill Malovitsa
Kyrylo Perevozchikov
Kyungmin Lee
Lars P. Søndergaard
Liu Hua
Louis-Philippe Huberdeau
Loïc Lajeanne
Lu Gong
Expand Down
2 changes: 1 addition & 1 deletion aiohttp/client_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def close(self) -> None:
self._drop_timeout()

def is_connected(self) -> bool:
return self.transport is not None
return self.transport is not None and not self.transport.is_closing()

def connection_lost(self, exc: Optional[BaseException]) -> None:
self._drop_timeout()
Expand Down
10 changes: 10 additions & 0 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ def _cleanup(self) -> None:
transport)
else:
alive.append((proto, use_time))
else:
transport = proto.transport
proto.close()
if key.is_ssl and not self._cleanup_closed_disabled:
self._cleanup_closed_transports.append(transport)

if alive:
connections[key] = alive
Expand Down Expand Up @@ -569,6 +574,11 @@ def _get(self, key: 'ConnectionKey') -> Optional[ResponseHandler]:
# The very last connection was reclaimed: drop the key
del self._conns[key]
return proto
else:
transport = proto.transport
proto.close()
if key.is_ssl and not self._cleanup_closed_disabled:
self._cleanup_closed_transports.append(transport)

# No more connections: drop the key
del self._conns[key]
Expand Down
32 changes: 32 additions & 0 deletions tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,38 @@ async def test_get(loop) -> None:
conn.close()


async def test_get_unconnected_proto(loop) -> None:
conn = aiohttp.BaseConnector()
key = ConnectionKey('localhost', 80, False, None, None, None, None)
assert conn._get(key) is None

proto = create_mocked_conn(loop)
conn._conns[key] = [(proto, loop.time())]
assert conn._get(key) == proto

assert conn._get(key) is None
conn._conns[key] = [(proto, loop.time())]
proto.is_connected = lambda *args: False
assert conn._get(key) is None
await conn.close()


async def test_get_unconnected_proto_ssl(loop) -> None:
conn = aiohttp.BaseConnector()
key = ConnectionKey('localhost', 80, True, None, None, None, None)
assert conn._get(key) is None

proto = create_mocked_conn(loop)
conn._conns[key] = [(proto, loop.time())]
assert conn._get(key) == proto

assert conn._get(key) is None
conn._conns[key] = [(proto, loop.time())]
proto.is_connected = lambda *args: False
assert conn._get(key) is None
await conn.close()


async def test_get_expired(loop) -> None:
conn = aiohttp.BaseConnector(loop=loop)
key = ConnectionKey('localhost', 80, False, None, None, None, None)
Expand Down

0 comments on commit e80df69

Please sign in to comment.