@@ -29,8 +29,11 @@ func TestMedian(t *testing.T) {
2929// The loaded sampler must return nil when the phase is too short or yields too
3030// few samples - a number from an unsaturated instant would be misleading.
3131func TestLoadSamplerGating (t * testing.T ) {
32+ origFails := lulFails
33+ t .Cleanup (func () { lulFails = origFails })
3234 // Real network unavailable in tests isn't assumed; we only exercise the
33- // gating path: stop immediately, far under both thresholds.
35+ // gating path: stop immediately, far under both thresholds. The empty address
36+ // is the offline part - the dial fails without touching a resolver.
3437 stop := startLoadSampler (context .Background (), "" )
3538 time .Sleep (50 * time .Millisecond )
3639 if got := stop (); got != nil {
@@ -43,11 +46,23 @@ func TestLoadSamplerGating(t *testing.T) {
4346// (The old sync.Once cached the miss and then dialed the hostname - paying DNS in
4447// every timed handshake - for the whole process.)
4548func TestLulDialAddrRetriesAfterResolveFailure (t * testing.T ) {
46- origTarget , origResolved , origDial := lulTarget , lulResolved , lulResolveDial
47- t .Cleanup (func () { lulTarget , lulResolved , lulResolveDial = origTarget , origResolved , origDial })
49+ origTarget , origResolved , origFails , origDial := lulTarget , lulResolved , lulFails , lulResolveDial
50+ origBurst , origFam := lulProbeBurst , lulFamilyDial
51+ t .Cleanup (func () {
52+ lulTarget , lulResolved , lulFails , lulResolveDial = origTarget , origResolved , origFails , origDial
53+ lulProbeBurst , lulFamilyDial = origBurst , origFam
54+ })
4855
4956 lulTarget = "lul.invalid:443" // a hostname, so lulDialAddr takes the resolve path
50- lulResolved = ""
57+ lulResolved , lulFails = "" , 0
58+ // Selection is not under test here: report the candidate clean so the
59+ // caching semantics stay the subject, and keep both seams off the network.
60+ lulProbeBurst = func (context.Context , string , int , time.Duration ) ([]float64 , int ) {
61+ return []float64 {1 , 1 , 1 , 1 , 1 }, 0
62+ }
63+ lulFamilyDial = func (context.Context , string , string ) (net.Conn , error ) {
64+ return nil , errors .New ("not under test" )
65+ }
5166 calls := 0
5267 lulResolveDial = func (context.Context , string ) (net.Conn , error ) {
5368 calls ++
@@ -58,47 +73,111 @@ func TestLulDialAddrRetriesAfterResolveFailure(t *testing.T) {
5873 }
5974
6075 // 1st: resolve fails -> hostname fallback, nothing cached.
61- if got := lulDialAddr (); got != lulTarget {
76+ if got := lulDialAddr (context . Background () ); got != lulTarget {
6277 t .Fatalf ("after failed resolve = %q, want hostname fallback %q" , got , lulTarget )
6378 }
6479 // 2nd: the miss was NOT cached, so the resolve retries and now succeeds.
65- if got , want := lulDialAddr (), "203.0.113.7:443" ; got != want {
80+ if got , want := lulDialAddr (context . Background () ), "203.0.113.7:443" ; got != want {
6681 t .Fatalf ("after recovered resolve = %q, want resolved literal %q" , got , want )
6782 }
6883 // 3rd: served from cache, no further dial.
69- if got , want := lulDialAddr (), "203.0.113.7:443" ; got != want {
84+ if got , want := lulDialAddr (context . Background () ), "203.0.113.7:443" ; got != want {
7085 t .Fatalf ("cached resolve = %q, want %q" , got , want )
7186 }
7287 if calls != 2 {
7388 t .Fatalf ("resolve dialed %d times, want 2 (one failed retry, then one cached success)" , calls )
7489 }
7590}
7691
77- // A cached resolve must not outlive the stack it belongs to: after
78- // lulFailInvalidate consecutive failed connects the literal is dropped so the
79- // next sample re-resolves (the first resolve picked IPv6, IPv6 later died while
80- // IPv4 still works). A successful connect resets the streak.
81- func TestLulNoteConnectDropsDeadCache (t * testing.T ) {
82- origResolved , origFails := lulResolved , lulFails
83- t .Cleanup (func () { lulResolved , lulFails = origResolved , origFails })
92+ // A RUN PINS ONE ENDPOINT EVEN WHEN THE RESOLVE FAILS. Handing back "" would
93+ // leave every sample to resolve for itself: on an idle link that is a whole
94+ // family selection per probe, so the idle burst spends its budget on failed
95+ // selections instead of probes and reports no baseline at all; inside a load
96+ // phase it is a DNS lookup in every timed handshake, inflating the bufferbloat
97+ // number being measured. The hostname is the honest fallback - worse than a
98+ // literal, but one name for every sample of the run.
99+ func TestLulRunEndpointPinsHostnameWhenResolveFails (t * testing.T ) {
100+ lulSelectSeams (t )
101+ resolves := 0
102+ lulResolveDial = func (context.Context , string ) (net.Conn , error ) {
103+ resolves ++
104+ return nil , errors .New ("simulated resolve failure" )
105+ }
106+ lulProbeBurst = func (context.Context , string , int , time.Duration ) ([]float64 , int ) {
107+ t .Error ("validated a candidate although the resolve failed" )
108+ return nil , lulSelectProbes
109+ }
110+ lulFamilyDial = func (context.Context , string , string ) (net.Conn , error ) {
111+ return nil , errors .New ("no candidate: must not be reached" )
112+ }
113+
114+ if got := lulRunEndpoint (context .Background ()); got != lulTarget {
115+ t .Fatalf ("lulRunEndpoint = %q with the resolve failing, want the hostname %q: \" \" " +
116+ "leaves every sample to resolve for itself" , got , lulTarget )
117+ }
118+ // And a sample dials what it was handed, whatever the cache holds: no
119+ // selection, no DNS, nothing but the timed handshake.
120+ before := resolves
121+ if _ , ok := connectRTT (context .Background (), "" ); ok {
122+ t .Fatal ("connectRTT reported a successful sample without an address to dial" )
123+ }
124+ if resolves != before {
125+ t .Fatalf ("one sample ran %d extra resolve dials, want 0: a sample must dial the " +
126+ "endpoint the run pinned, never re-enter the selection" , resolves - before )
127+ }
128+ }
129+
130+ // A CACHED LITERAL IS DROPPED AT A RUN BOUNDARY, NOT MID-RUN. The cache must not
131+ // outlive the stack it belongs to: after lulFailInvalidate consecutive failed
132+ // connects the literal is dead and the next run must re-resolve (the first
133+ // resolve picked IPv6, IPv6 later died while IPv4 still works). But dropping it
134+ // the moment the streak lands drops it INSIDE a saturated phase - congestion
135+ // produces those failures too - and the phases after it are then left dialing a
136+ // hostname, paying DNS inside the very handshakes being timed. So lulNoteConnect
137+ // only counts and lulDialAddr, reached once per run on an idle link, does the
138+ // dropping. A successful connect clears the streak: a path that recovered is not
139+ // dead.
140+ func TestLulDeadLiteralDroppedAtRunBoundary (t * testing.T ) {
141+ lulSelectSeams (t )
142+ lulResolved = "203.0.113.7:443"
143+ resolves := 0
144+ lulResolveDial = func (context.Context , string ) (net.Conn , error ) {
145+ resolves ++
146+ return stubConn {remote : & net.TCPAddr {IP : net .ParseIP ("2001:db8::7" ), Port : 443 }}, nil
147+ }
148+ lulProbeBurst = func (context.Context , string , int , time.Duration ) ([]float64 , int ) {
149+ return []float64 {7.0 , 7.2 , 6.9 , 7.1 , 7.0 }, 0
150+ }
151+ lulFamilyDial = func (context.Context , string , string ) (net.Conn , error ) {
152+ return nil , errors .New ("clean winner: must not consult the other family" )
153+ }
84154
85- lulResolved , lulFails = "203.0.113.7:443" , 0
86155 for i := 0 ; i < lulFailInvalidate - 1 ; i ++ {
87156 lulNoteConnect (false )
88157 }
89- if lulResolved == "" {
90- t .Fatalf ("cache dropped after %d failures, want it kept below the threshold" , lulFailInvalidate - 1 )
91- }
92- lulNoteConnect (true ) // success resets the streak
158+ lulNoteConnect (true ) // a success clears the streak
93159 for i := 0 ; i < lulFailInvalidate - 1 ; i ++ {
94160 lulNoteConnect (false )
95161 }
162+ if got := lulRunEndpoint (context .Background ()); got != "203.0.113.7:443" {
163+ t .Fatalf ("lulRunEndpoint = %q below the failure threshold, want the cached literal: " +
164+ "only a streak that reaches lulFailInvalidate=%d means dead" , got , lulFailInvalidate )
165+ }
166+ if resolves != 0 {
167+ t .Fatalf ("re-resolved %d times below the failure threshold, want 0" , resolves )
168+ }
169+
170+ lulNoteConnect (false ) // the streak lands, mid-run
96171 if lulResolved == "" {
97- t .Fatal ("a successful connect must reset the failure streak" )
172+ t .Fatal ("the cached literal was dropped the moment the streak landed; want it held to " +
173+ "the run boundary, so no later phase is left dialing a hostname mid-run" )
174+ }
175+ if got := lulRunEndpoint (context .Background ()); got != v6Winner {
176+ t .Fatalf ("lulRunEndpoint = %q on the next run, want the re-selected literal %q: a dead " +
177+ "literal must not survive a run boundary either" , got , v6Winner )
98178 }
99- lulNoteConnect (false ) // streak reaches the threshold
100- if lulResolved != "" {
101- t .Fatalf ("cache = %q after %d consecutive failures, want dropped for re-resolve" , lulResolved , lulFailInvalidate )
179+ if resolves != 1 {
180+ t .Fatalf ("re-resolved %d times at the run boundary, want exactly 1" , resolves )
102181 }
103182}
104183
0 commit comments