Skip to content

Commit 7e7f11e

Browse files
Add a new test to validate the lookahead race condition
1 parent 6943dcf commit 7e7f11e

File tree

1 file changed

+54
-1
lines changed

1 file changed

+54
-1
lines changed

tests/test_channel.py

+54-1
Original file line numberDiff line numberDiff line change
@@ -805,11 +805,12 @@ def app_check_disconnect(self, environ, start_response):
805805
)
806806
return [body]
807807

808-
def _make_app_with_lookahead(self):
808+
def _make_app_with_lookahead(self, recv_bytes=8192):
809809
"""
810810
Setup a channel with lookahead and store it and the socket in self
811811
"""
812812
adj = DummyAdjustments()
813+
adj.recv_bytes = recv_bytes
813814
adj.channel_request_lookahead = 5
814815
channel, sock, map = self._makeOneWithMap(adj=adj)
815816
channel.server.application = self.app_check_disconnect
@@ -901,6 +902,58 @@ def test_lookahead_continue(self):
901902
self.assertEqual(data.split("\r\n")[-1], "finished")
902903
self.assertEqual(self.request_body, b"x")
903904

905+
def test_lookahead_bad_request_drop_extra_data(self):
906+
"""
907+
Send two requests, the first one being bad, split on the recv_bytes
908+
limit, then emulate a race that could happen whereby we read data from
909+
the socket while the service thread is cleaning up due to an error
910+
processing the request.
911+
"""
912+
913+
invalid_request = [
914+
"GET / HTTP/1.1",
915+
"Host: localhost:8080",
916+
"Content-length: -1",
917+
"",
918+
]
919+
920+
invalid_request_len = len("".join([x + "\r\n" for x in invalid_request]))
921+
922+
second_request = [
923+
"POST / HTTP/1.1",
924+
"Host: localhost:8080",
925+
"Content-Length: 1",
926+
"",
927+
"x",
928+
]
929+
930+
full_request = invalid_request + second_request
931+
932+
self._make_app_with_lookahead(recv_bytes=invalid_request_len)
933+
self._send(*full_request)
934+
self.channel.handle_read()
935+
self.assertEqual(len(self.channel.requests), 1)
936+
self.channel.server.tasks[0].service()
937+
self.assertTrue(self.channel.close_when_flushed)
938+
# Read all of the next request
939+
self.channel.handle_read()
940+
self.channel.handle_read()
941+
# Validate that there is no more data to be read
942+
self.assertEqual(self.sock.remote.local_sent, b"")
943+
# Validate that we dropped the data from the second read, and did not
944+
# create a new request
945+
self.assertEqual(len(self.channel.requests), 0)
946+
data = self.sock.recv(256).decode("ascii")
947+
self.assertFalse(self.channel.readable())
948+
self.assertTrue(self.channel.writable())
949+
950+
# Handle the write, which will close the socket
951+
self.channel.handle_write()
952+
self.assertTrue(self.sock.closed)
953+
954+
data = self.sock.recv(256)
955+
self.assertEqual(len(data), 0)
956+
904957

905958
class DummySock:
906959
blocking = False

0 commit comments

Comments
 (0)