Skip to content

Commit 42b8fc3

Browse files
authored
Merge pull request #1601 from njsmith/give-up-on-measuring-backlog
Give up on measuring TCP socket backlog
2 parents eadb499 + 82246c9 commit 42b8fc3

File tree

1 file changed

+27
-59
lines changed

1 file changed

+27
-59
lines changed

trio/tests/test_highlevel_open_tcp_listeners.py

Lines changed: 27 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -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
11556
async 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

Comments
 (0)