Skip to content

Commit dda1f81

Browse files
committed
fix flakyness on test_no_delay: introduced statistical margin instead of fixed amount to reduce flakiness
1 parent 1e4fdd2 commit dda1f81

1 file changed

Lines changed: 49 additions & 36 deletions

File tree

tests/test_connection.py

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
)
1616
from pyln.testing.utils import VALGRIND, EXPERIMENTAL_DUAL_FUND, FUNDAMOUNT, RUST, SLOW_MACHINE
1717

18+
import math
1819
import os
1920
import pytest
2021
import random
@@ -24,6 +25,7 @@
2425
import websocket
2526
import signal
2627
import ssl
28+
import sys
2729

2830

2931
def test_connect_basic(node_factory):
@@ -4649,7 +4651,15 @@ def test_private_channel_no_reconnect(node_factory):
46494651

46504652
@pytest.mark.slow_test
46514653
def test_no_delay(node_factory):
4652-
"""Is our Nagle disabling for critical messages working?"""
4654+
"""Disabling Nagle for critical messages should speed up payment round-trips.
4655+
4656+
This is timing-based, so we compare it statistically: time N round-trips
4657+
with and without Nagle and compare the per-trip means with a K=3
4658+
standard-error margin (a spurious result is then a ~3-sigma, ~0.1% event).
4659+
The speedup only exists where the TCP Nagle timer is real (~200ms on Linux);
4660+
macOS loopback has no measurable timer, so there we only assert that
4661+
disabling Nagle isn't slower.
4662+
"""
46534663
l1, l2 = node_factory.line_graph(2, opts={'dev-keep-nagle': None,
46544664
'may_reconnect': True})
46554665

@@ -4662,56 +4672,59 @@ def test_no_delay(node_factory):
46624672
}
46634673

46644674
def do_round_trips(n):
4665-
start = time.time()
4675+
"""Return the list of per-trip durations (seconds) for n round-trips."""
4676+
times = []
46664677
for _ in range(n):
46674678
phash = random.randbytes(32).hex()
4679+
start = time.time()
46684680
l1.rpc.sendpay([routestep], phash)
46694681
with pytest.raises(RpcError, match="WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS"):
46704682
l1.rpc.waitsendpay(phash)
4671-
return time.time() - start
4683+
times.append(time.time() - start)
4684+
return times
4685+
4686+
def mean(xs):
4687+
return sum(xs) / len(xs)
46724688

4673-
# Probe the actual per-RTT Nagle overhead on this platform with a small
4674-
# sample (10 trips), then scale up for the full run. Linux's TCP Nagle
4675-
# timer fires after ~200ms; macOS/loopback may be much shorter.
4676-
PROBE = 10
4677-
probe_nagle = do_round_trips(PROBE)
4689+
def variance(xs):
4690+
# Sample variance (Bessel-corrected).
4691+
m = mean(xs)
4692+
return sum((x - m) ** 2 for x in xs) / (len(xs) - 1)
46784693

4679-
# Test with nagle (full run)
4680-
nagle_time = do_round_trips(100)
4694+
N = 100
4695+
4696+
nagle_trips = do_round_trips(N)
4697+
nagle_time = sum(nagle_trips)
46814698

46824699
del l1.daemon.opts['dev-keep-nagle']
46834700
del l2.daemon.opts['dev-keep-nagle']
46844701
l1.restart()
46854702
l2.restart()
46864703
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
46874704

4688-
# Probe without nagle
4689-
probe_normal = do_round_trips(PROBE)
4690-
4691-
# Test without nagle (full run)
4692-
normal_time = do_round_trips(100)
4693-
4694-
# Estimate the per-RTT Nagle delay from the probe; average delay is half
4695-
# the timer period. Use half again as variance margin (same logic as the
4696-
# original 200ms assumption). If the platform shows no measurable Nagle
4697-
# effect (e.g. macOS loopback with a very short timer) the expected saving
4698-
# rounds down to zero and we only assert directional ordering.
4699-
per_rtt_delay = max(0.0, (probe_nagle - probe_normal) / PROBE)
4700-
expected_saving = 100 * per_rtt_delay / 2
4701-
4702-
print(f"Nagle probe: {probe_nagle:.3f}s nagle, {probe_normal:.3f}s normal, "
4703-
f"per-RTT overhead ~{per_rtt_delay * 1000:.1f}ms, "
4704-
f"expected saving {expected_saving:.2f}s")
4705-
4706-
if expected_saving > 0.5:
4707-
# Platform shows a meaningful Nagle effect: assert at least half the saving.
4708-
# The 10-sample probe can overestimate per-RTT delay by ~2x due to variance,
4709-
# so apply an extra safety factor here.
4710-
assert normal_time < nagle_time - expected_saving / 2
4705+
normal_trips = do_round_trips(N)
4706+
normal_time = sum(normal_trips)
4707+
4708+
mean_nagle = mean(nagle_trips)
4709+
mean_normal = mean(normal_trips)
4710+
stderr = math.sqrt(variance(nagle_trips) / len(nagle_trips)
4711+
+ variance(normal_trips) / len(normal_trips))
4712+
4713+
# Margin = K standard errors of the difference of the per-trip means.
4714+
K = 3
4715+
margin = K * stderr
4716+
4717+
print(f"Nagle: mean trip {mean_nagle * 1000:.1f}ms with vs "
4718+
f"{mean_normal * 1000:.1f}ms without; saving "
4719+
f"{(mean_nagle - mean_normal) * 1000:.1f}ms, {K}-sigma margin "
4720+
f"{margin * 1000:.1f}ms (totals {nagle_time:.1f}s vs {normal_time:.1f}s)")
4721+
4722+
if sys.platform.startswith('linux'):
4723+
# Linux has the ~200ms Nagle timer: disabling it should be significantly faster.
4724+
assert mean_normal < mean_nagle - margin
47114725
else:
4712-
# Platform Nagle delay is too small to measure reliably (e.g. macOS);
4713-
# just assert that disabling Nagle is not slower.
4714-
assert normal_time <= nagle_time + 1.0
4726+
# No measurable timer (e.g. macOS loopback): just assert it's not slower.
4727+
assert mean_normal <= mean_nagle + margin
47154728

47164729

47174730
def test_listpeerchannels_by_scid(node_factory):

0 commit comments

Comments
 (0)