ZaStoGram_desktop/Telegram/SourceFiles/tests/test_mtproxy_latency_costs.py
loop-uh 7b3bd451f5 Stop paying six seconds of queue and a round trip of Nagle
Two client-side costs, both measured against live relays rather than
argued about.

The dial pacer spaced every attempt to one relay a quarter second
apart. A cold start opens tens of sockets, so the later ones waited
seconds before their first byte - the client log shows it plainly,
tcp_ms climbing past eight seconds while the network round trip is
seventeen milliseconds. The premise for paying that was arriving as
separate clients rather than as one burst; it does not survive what
was measured since. The filtered network keys on the shape of the
hello and the name in SNI, not on arrival timing, and twenty-four
completely unpaced dials reached resPQ 24 out of 24 with the whole
batch done in 170ms. Spacing drops to 50ms - still distinct
arrivals, a ramp that costs a second instead of six. The failure
backoff, which is what actually protects a relay that swallows the
overflow, is untouched.

Nagle was never disabled. mtproto writes a short request and waits,
which is the exact case it penalises. Both transports now set
LowDelayOption in the connected handler - not in the constructor
next to the buffer sizes, because Qt applies socket options through
the socket engine and that does not exist until the connection is
up.

Guards for both, with the numbers next to the constant so the next
person to raise it argues with the measurement.
2026-07-26 19:08:19 +03:00

68 lines
3.2 KiB
Python

"""Two client-side costs that showed up as "the proxy is slow".
Measured 26 July 2026 against live relays. Twenty-four simultaneous dials,
paced by nothing at all, reached resPQ 24 out of 24 with the whole batch done
in 170 ms. The same twenty-four through the client's own pacer cost about six
seconds of queue at a quarter second apiece, and a cold start pays that before
the first message moves - the client log shows it directly as tcp_ms climbing
past eight seconds on the later sockets.
The premise the quarter second rested on was that arriving as separate clients
is worth something against a filtered network. It is not: what that network
keys on was measured, and it is the exact shape of the hello plus the name in
SNI. A hello matching neither is answered however it arrives.
The other cost is Nagle. mtproto writes a short request and then waits, which
is the case Nagle penalises, and Qt applies socket options through the socket
engine - which does not exist until the connection is up. So the option has to
be set in the connected handler, not next to the buffer sizes in the
constructor, or it is silently dropped.
"""
from pathlib import Path
SOURCE_DIR = Path(__file__).resolve().parents[1]
PACER_CPP = SOURCE_DIR / "mtproto" / "proxy" / "dial_pacer.cpp"
TLS_HANDSHAKE_CPP = (SOURCE_DIR / "mtproto" / "proxy" / "mtproxy"
/ "tls_socket_handshake.cpp")
TCP_SOCKET_CPP = (SOURCE_DIR / "mtproto" / "transport" / "details"
/ "mtproto_tcp_socket.cpp")
def test_dial_spacing_stays_small_enough_for_a_fast_ramp():
source = PACER_CPP.read_text(encoding="utf-8")
line = next(l for l in source.splitlines()
if "kDialSpacing = crl::time(" in l)
spacing = int(line.split("crl::time(")[1].split(")")[0])
# A full ramp is tens of sockets. At 250 ms that was six seconds of queue
# before anything moved; the measurement that justified paying it does not
# hold. Keep some spacing - a relay's own logs read better with distinct
# arrivals - but not enough to be felt.
assert 0 < spacing <= 80, spacing
# The numbers belong next to the constant, so whoever raises it again has
# to argue with them rather than with a guess.
assert "170ms" in source
assert "twenty-four" in source
def test_failure_backoff_survives_the_smaller_spacing():
source = PACER_CPP.read_text(encoding="utf-8")
# A relay that really does swallow the overflow is handled by the failure
# spacing, and that is the part the smaller unconditional spacing leans
# on. It must not be weakened along with it.
assert "kFailureSpacingStep = crl::time(500)" in source
assert "kFailureSpacingMax = crl::time(4000)" in source
assert "kFailuresBeforeBackoff = 4" in source
def test_nagle_is_disabled_on_both_transports_after_connecting():
for path in (TLS_HANDSHAKE_CPP, TCP_SOCKET_CPP):
source = path.read_text(encoding="utf-8")
assert "QAbstractSocket::LowDelayOption" in source, path.name
# In the connected handler, not the constructor: Qt drops options set
# before the socket engine exists.
head = source.split("LowDelayOption", 1)[0]
assert ("plainConnected" in head
or "QTcpSocket::connected" in head), path.name