1515)
1616from pyln .testing .utils import VALGRIND , EXPERIMENTAL_DUAL_FUND , FUNDAMOUNT , RUST , SLOW_MACHINE
1717
18+ import math
1819import os
1920import pytest
2021import random
2425import websocket
2526import signal
2627import ssl
28+ import sys
2729
2830
2931def test_connect_basic (node_factory ):
@@ -4649,7 +4651,15 @@ def test_private_channel_no_reconnect(node_factory):
46494651
46504652@pytest .mark .slow_test
46514653def 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
47174730def test_listpeerchannels_by_scid (node_factory ):
0 commit comments