1
1
import enum
2
2
import logging
3
+ import random
3
4
import ssl
4
5
import time
5
6
from types import TracebackType
@@ -56,10 +57,12 @@ def __init__(
56
57
origin : Origin ,
57
58
stream : AsyncNetworkStream ,
58
59
keepalive_expiry : Optional [float ] = None ,
60
+ socket_poll_interval_between : Tuple [float , float ] = (1 , 3 ),
59
61
) -> None :
60
62
self ._origin = origin
61
63
self ._network_stream = stream
62
- self ._keepalive_expiry : Optional [float ] = keepalive_expiry
64
+ self ._keepalive_expiry = keepalive_expiry
65
+ self ._socket_poll_interval_between = socket_poll_interval_between
63
66
self ._expire_at : Optional [float ] = None
64
67
self ._state = HTTPConnectionState .NEW
65
68
self ._state_lock = AsyncLock ()
@@ -68,6 +71,8 @@ def __init__(
68
71
our_role = h11 .CLIENT ,
69
72
max_incomplete_event_size = self .MAX_INCOMPLETE_EVENT_SIZE ,
70
73
)
74
+ # Assuming we were just connected
75
+ self ._network_stream_used_at = time .monotonic ()
71
76
72
77
async def handle_async_request (self , request : Request ) -> Response :
73
78
if not self .can_handle_request (request .url .origin ):
@@ -173,6 +178,7 @@ async def _send_event(
173
178
bytes_to_send = self ._h11_state .send (event )
174
179
if bytes_to_send is not None :
175
180
await self ._network_stream .write (bytes_to_send , timeout = timeout )
181
+ self ._network_stream_used_at = time .monotonic ()
176
182
177
183
# Receiving the response...
178
184
@@ -224,6 +230,7 @@ async def _receive_event(
224
230
data = await self ._network_stream .read (
225
231
self .READ_NUM_BYTES , timeout = timeout
226
232
)
233
+ self ._network_stream_used_at = time .monotonic ()
227
234
228
235
# If we feed this case through h11 we'll raise an exception like:
229
236
#
@@ -281,16 +288,28 @@ def is_available(self) -> bool:
281
288
def has_expired (self ) -> bool :
282
289
now = time .monotonic ()
283
290
keepalive_expired = self ._expire_at is not None and now > self ._expire_at
291
+ if keepalive_expired :
292
+ return True
284
293
285
294
# If the HTTP connection is idle but the socket is readable, then the
286
295
# only valid state is that the socket is about to return b"", indicating
287
296
# a server-initiated disconnect.
288
- server_disconnected = (
289
- self ._state == HTTPConnectionState .IDLE
290
- and self ._network_stream .get_extra_info ("is_readable" )
291
- )
297
+ # Checking the readable status is relatively expensive so check it at a lower frequency.
298
+ if (now - self ._network_stream_used_at ) > self ._socket_poll_interval ():
299
+ self ._network_stream_used_at = now
300
+ server_disconnected = (
301
+ self ._state == HTTPConnectionState .IDLE
302
+ and self ._network_stream .get_extra_info ("is_readable" )
303
+ )
304
+ if server_disconnected :
305
+ return True
306
+
307
+ return False
292
308
293
- return keepalive_expired or server_disconnected
309
+ def _socket_poll_interval (self ) -> float :
310
+ # Randomize to avoid polling for all the connections at once
311
+ low , high = self ._socket_poll_interval_between
312
+ return random .uniform (low , high )
294
313
295
314
def is_idle (self ) -> bool :
296
315
return self ._state == HTTPConnectionState .IDLE
0 commit comments