@@ -176,6 +176,20 @@ def set_protocol(self, protocol):
176176 pass
177177
178178
179+ class MockTimerHandle :
180+ def __init__ (self , loop_later_list , delay , callback , args ):
181+ self .loop_later_list = loop_later_list
182+ self .delay = delay
183+ self .callback = callback
184+ self .args = args
185+ self .cancelled = False
186+
187+ def cancel (self ):
188+ if not self .cancelled :
189+ self .cancelled = True
190+ self .loop_later_list .remove (self )
191+
192+
179193class MockLoop :
180194 def __init__ (self ):
181195 self ._tasks = []
@@ -186,18 +200,20 @@ def create_task(self, coroutine):
186200 return MockTask ()
187201
188202 def call_later (self , delay , callback , * args ):
189- self ._later .insert (0 , (delay , callback , args ))
203+ handle = MockTimerHandle (self ._later , delay , callback , args )
204+ self ._later .insert (0 , handle )
205+ return handle
190206
191207 async def run_one (self ):
192208 return await self ._tasks .pop ()
193209
194210 def run_later (self , with_delay ):
195211 later = []
196- for delay , callback , args in self ._later :
197- if with_delay >= delay :
198- callback (* args )
212+ for timer_handle in self ._later :
213+ if with_delay >= timer_handle . delay :
214+ timer_handle . callback (* timer_handle . args )
199215 else :
200- later .append (( delay , callback , args ) )
216+ later .append (timer_handle )
201217 self ._later = later
202218
203219
@@ -315,6 +331,32 @@ async def test_keepalive_timeout(http_protocol_cls: HTTPProtocol):
315331 assert protocol .transport .is_closing ()
316332
317333
334+ @pytest .mark .anyio
335+ async def test_keepalive_timeout_with_pipelined_requests (
336+ http_protocol_cls : HTTPProtocol ,
337+ ):
338+ app = Response ("Hello, world" , media_type = "text/plain" )
339+
340+ protocol = get_connected_protocol (app , http_protocol_cls )
341+ protocol .data_received (SIMPLE_GET_REQUEST )
342+ protocol .data_received (SIMPLE_GET_REQUEST )
343+
344+ # After processing the first request, the keep-alive task should be
345+ # disabled because the second request is not responded yet.
346+ await protocol .loop .run_one ()
347+ assert b"HTTP/1.1 200 OK" in protocol .transport .buffer
348+ assert b"Hello, world" in protocol .transport .buffer
349+ assert protocol .timeout_keep_alive_task is None
350+
351+ # Process the second request and ensure that the keep-alive task
352+ # has been enabled again as the connection is now idle.
353+ protocol .transport .clear_buffer ()
354+ await protocol .loop .run_one ()
355+ assert b"HTTP/1.1 200 OK" in protocol .transport .buffer
356+ assert b"Hello, world" in protocol .transport .buffer
357+ assert protocol .timeout_keep_alive_task is not None
358+
359+
318360@pytest .mark .anyio
319361async def test_close (http_protocol_cls : HTTPProtocol ):
320362 app = Response (b"" , status_code = 204 , headers = {"connection" : "close" })
0 commit comments