@@ -52,65 +52,6 @@ async def test_open_tcp_listeners_specific_port_specific_host():
5252 assert listener .socket .getsockname () == (host , port )
5353
5454
55- # Warning: this sleeps, and needs to use a real sleep -- MockClock won't
56- # work.
57- #
58- # Also, this measurement technique often works, but not always: sometimes SYN
59- # cookies get triggered, and then the backlog measured this way effectively
60- # becomes infinite. (In particular, this has been observed happening on
61- # Travis-CI.) To avoid this blowing up and eating all FDs / ephemeral ports,
62- # we put an upper limit on the number of connections we attempt, and if we hit
63- # it then we return the magic string "lots". Then
64- # test_open_tcp_listeners_backlog uses a special path to handle this, treating
65- # it as a success -- but at least we'll see in coverage if none of our test
66- # runs are actually running the test properly.
67- async def measure_backlog (listener , limit ):
68- client_streams = []
69- try :
70- while True :
71- # Generally the response to the listen buffer being full is that
72- # the SYN gets dropped, and the client retries after 1 second. So
73- # we assume that any connect() call to localhost that takes >0.5
74- # seconds indicates a dropped SYN.
75- #
76- # Exception: on FreeBSD when the backlog is exhausted, connect
77- # has been observed to sometimes raise ConnectionResetError.
78- with trio .move_on_after (0.5 ) as cancel_scope :
79- try :
80- client_stream = await open_stream_to_socket_listener (listener )
81- except ConnectionResetError : # pragma: no cover
82- break
83- client_streams .append (client_stream )
84- if cancel_scope .cancelled_caught :
85- break
86- if len (client_streams ) >= limit : # pragma: no cover
87- return "lots"
88- finally :
89- # The need for "no cover" here is subtle: see
90- # https://github.com/python-trio/trio/issues/522
91- for client_stream in client_streams : # pragma: no cover
92- await client_stream .aclose ()
93-
94- return len (client_streams )
95-
96-
97- @slow
98- async def test_open_tcp_listeners_backlog ():
99- # Operating systems don't necessarily use the exact backlog you pass
100- async def check_backlog (nominal , required_min , required_max ):
101- listeners = await open_tcp_listeners (0 , backlog = nominal )
102- actual = await measure_backlog (listeners [0 ], required_max + 10 )
103- for listener in listeners :
104- await listener .aclose ()
105- print ("nominal" , nominal , "actual" , actual )
106- if actual == "lots" : # pragma: no cover
107- return
108- assert required_min <= actual <= required_max
109-
110- await check_backlog (nominal = 1 , required_min = 1 , required_max = 10 )
111- await check_backlog (nominal = 11 , required_min = 11 , required_max = 20 )
112-
113-
11455@binds_ipv6
11556async def test_open_tcp_listeners_ipv6_v6only ():
11657 # Check IPV6_V6ONLY is working properly
@@ -175,6 +116,7 @@ class FakeSocket(tsocket.SocketType):
175116
176117 closed = attr .ib (default = False )
177118 poison_listen = attr .ib (default = False )
119+ backlog = attr .ib (default = None )
178120
179121 def getsockopt (self , level , option ):
180122 if (level , option ) == (tsocket .SOL_SOCKET , tsocket .SO_ACCEPTCONN ):
@@ -188,6 +130,9 @@ async def bind(self, sockaddr):
188130 pass
189131
190132 def listen (self , backlog ):
133+ assert self .backlog is None
134+ assert backlog is not None
135+ self .backlog = backlog
191136 if self .poison_listen :
192137 raise FakeOSError ("whoops" )
193138
@@ -325,3 +270,26 @@ async def test_open_tcp_listeners_socket_fails_not_afnosupport():
325270 assert exc_info .value .errno == errno .EINVAL
326271 assert exc_info .value .__cause__ is None
327272 assert "nope" in str (exc_info .value )
273+
274+
275+ # We used to have an elaborate test that opened a real TCP listening socket
276+ # and then tried to measure its backlog by making connections to it. And most
277+ # of the time, it worked. But no matter what we tried, it was always fragile,
278+ # because it had to do things like use timeouts to guess when the listening
279+ # queue was full, sometimes the CI hosts go into SYN-cookie mode (where there
280+ # effectively is no backlog), sometimes the host might not be enough resources
281+ # to give us the full requested backlog... it was a mess. So now we just check
282+ # that the backlog argument is passed through correctly.
283+ async def test_open_tcp_listeners_backlog ():
284+ fsf = FakeSocketFactory (99 )
285+ tsocket .set_custom_socket_factory (fsf )
286+ for (given , expected ) in [
287+ (None , 0xFFFF ),
288+ (99999999 , 0xFFFF ),
289+ (10 , 10 ),
290+ (1 , 1 ),
291+ ]:
292+ listeners = await open_tcp_listeners (0 , backlog = given )
293+ assert listeners
294+ for listener in listeners :
295+ assert listener .socket .backlog == expected
0 commit comments