2121 ConnectionNotAvailable ,
2222 LocalProtocolError ,
2323 RemoteProtocolError ,
24+ ServerDisconnectedError ,
2425 WriteError ,
2526 map_exceptions ,
2627)
@@ -45,6 +46,7 @@ class HTTPConnectionState(enum.IntEnum):
4546 ACTIVE = 1
4647 IDLE = 2
4748 CLOSED = 3
49+ SERVER_DISCONNECTED = 4
4850
4951
5052class AsyncHTTP11Connection (AsyncConnectionInterface ):
@@ -59,7 +61,7 @@ def __init__(
5961 ) -> None :
6062 self ._origin = origin
6163 self ._network_stream = stream
62- self ._keepalive_expiry : Optional [ float ] = keepalive_expiry
64+ self ._keepalive_expiry = keepalive_expiry
6365 self ._expire_at : Optional [float ] = None
6466 self ._state = HTTPConnectionState .NEW
6567 self ._state_lock = AsyncLock ()
@@ -76,13 +78,7 @@ async def handle_async_request(self, request: Request) -> Response:
7678 f"to { self ._origin } "
7779 )
7880
79- async with self ._state_lock :
80- if self ._state in (HTTPConnectionState .NEW , HTTPConnectionState .IDLE ):
81- self ._request_count += 1
82- self ._state = HTTPConnectionState .ACTIVE
83- self ._expire_at = None
84- else :
85- raise ConnectionNotAvailable ()
81+ await self ._update_state ()
8682
8783 try :
8884 kwargs = {"request" : request }
@@ -142,6 +138,29 @@ async def handle_async_request(self, request: Request) -> Response:
142138 await self ._response_closed ()
143139 raise exc
144140
141+ async def _update_state (self ) -> None :
142+ async with self ._state_lock :
143+ # If the HTTP connection is idle but the socket is readable, then the
144+ # only valid state is that the socket is about to return b"", indicating
145+ # a server-initiated disconnect.
146+ server_disconnected = (
147+ self ._state == HTTPConnectionState .IDLE
148+ and self ._network_stream .get_extra_info ("is_readable" )
149+ )
150+ if (
151+ server_disconnected
152+ or self ._state == HTTPConnectionState .SERVER_DISCONNECTED
153+ ):
154+ self ._state = HTTPConnectionState .SERVER_DISCONNECTED
155+ raise ServerDisconnectedError ()
156+
157+ if self ._state in (HTTPConnectionState .NEW , HTTPConnectionState .IDLE ):
158+ self ._request_count += 1
159+ self ._state = HTTPConnectionState .ACTIVE
160+ self ._expire_at = None
161+ else :
162+ raise ConnectionNotAvailable ()
163+
145164 # Sending the request...
146165
147166 async def _send_request_headers (self , request : Request ) -> None :
@@ -279,18 +298,13 @@ def is_available(self) -> bool:
279298 return self ._state == HTTPConnectionState .IDLE
280299
281300 def has_expired (self ) -> bool :
282- now = time .monotonic ()
283- keepalive_expired = self ._expire_at is not None and now > self ._expire_at
284-
285- # If the HTTP connection is idle but the socket is readable, then the
286- # only valid state is that the socket is about to return b"", indicating
287- # a server-initiated disconnect.
288- server_disconnected = (
289- self ._state == HTTPConnectionState .IDLE
290- and self ._network_stream .get_extra_info ("is_readable" )
291- )
301+ if self ._state == HTTPConnectionState .SERVER_DISCONNECTED :
302+ # Connection that is disconnected by the server is considered expired.
303+ # Pool then cleans up this connection by closing it.
304+ return True
292305
293- return keepalive_expired or server_disconnected
306+ now = time .monotonic ()
307+ return self ._expire_at is not None and now > self ._expire_at
294308
295309 def is_idle (self ) -> bool :
296310 return self ._state == HTTPConnectionState .IDLE
0 commit comments