Skip to content

Commit a1837ae

Browse files
backport: negative trace substraction when using SetTimeout
1 parent 2b771ba commit a1837ae

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

request.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -897,16 +897,31 @@ func (r *Request) TraceInfo() TraceInfo {
897897
return TraceInfo{}
898898
}
899899

900+
ct.lock.RLock()
901+
defer ct.lock.RUnlock()
902+
900903
ti := TraceInfo{
901-
DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
902-
TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
903-
ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
904+
DNSLookup: 0,
905+
TCPConnTime: 0,
906+
ServerTime: 0,
904907
IsConnReused: ct.gotConnInfo.Reused,
905908
IsConnWasIdle: ct.gotConnInfo.WasIdle,
906909
ConnIdleTime: ct.gotConnInfo.IdleTime,
907910
RequestAttempt: r.Attempt,
908911
}
909912

913+
if !ct.dnsStart.IsZero() && !ct.dnsDone.IsZero() {
914+
ti.DNSLookup = ct.dnsDone.Sub(ct.dnsStart)
915+
}
916+
917+
if !ct.tlsHandshakeDone.IsZero() && !ct.tlsHandshakeStart.IsZero() {
918+
ti.TLSHandshake = ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart)
919+
}
920+
921+
if !ct.gotFirstResponseByte.IsZero() && !ct.gotConn.IsZero() {
922+
ti.ServerTime = ct.gotFirstResponseByte.Sub(ct.gotConn)
923+
}
924+
910925
// Calculate the total time accordingly when connection is reused,
911926
// and DNS start and get conn time may be zero if the request is invalid.
912927
// See issue #1016.

request_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,83 @@ func TestTraceInfoOnTimeout(t *testing.T) {
21352135
assertEqual(t, true, tr.TotalTime == resp.Time())
21362136
}
21372137

2138+
func TestTraceInfoOnTimeoutWithSetTimeout(t *testing.T) {
2139+
t.Run("timeout with very short timeout", func(t *testing.T) {
2140+
client := New().
2141+
SetTimeout(1 * time.Millisecond).
2142+
SetBaseURL("http://resty-nowhere.local").
2143+
EnableTrace()
2144+
2145+
resp, err := client.R().Get("/")
2146+
assertNotNil(t, err)
2147+
assertNotNil(t, resp)
2148+
2149+
tr := resp.Request.TraceInfo()
2150+
2151+
assertEqual(t, true, tr.DNSLookup == 0)
2152+
assertEqual(t, true, tr.ConnTime == 0)
2153+
assertEqual(t, true, tr.TLSHandshake == 0)
2154+
assertEqual(t, true, tr.TCPConnTime == 0)
2155+
assertEqual(t, true, tr.ServerTime == 0)
2156+
assertEqual(t, true, tr.ResponseTime == 0)
2157+
assertEqual(t, true, tr.TotalTime > 0)
2158+
assertEqual(t, true, tr.TotalTime == resp.Time())
2159+
})
2160+
2161+
t.Run("successful request with SetTimeout", func(t *testing.T) {
2162+
ts := createGetServer(t)
2163+
defer ts.Close()
2164+
2165+
client := New().
2166+
SetTimeout(5 * time.Second).
2167+
SetBaseURL(ts.URL).
2168+
EnableTrace()
2169+
2170+
resp, err := client.R().Get("/")
2171+
assertNil(t, err)
2172+
assertNotNil(t, resp)
2173+
2174+
tr := resp.Request.TraceInfo()
2175+
2176+
assertEqual(t, true, tr.DNSLookup >= 0)
2177+
assertEqual(t, true, tr.ConnTime >= 0)
2178+
assertEqual(t, true, tr.TLSHandshake >= 0)
2179+
assertEqual(t, true, tr.TCPConnTime >= 0)
2180+
assertEqual(t, true, tr.ServerTime >= 0)
2181+
assertEqual(t, true, tr.ResponseTime >= 0)
2182+
assertEqual(t, true, tr.TotalTime > 0)
2183+
assertEqual(t, true, tr.TotalTime == resp.Time())
2184+
})
2185+
2186+
t.Run("HTTPS request with TLS handshake", func(t *testing.T) {
2187+
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2188+
w.WriteHeader(http.StatusOK)
2189+
_, _ = w.Write([]byte("OK"))
2190+
}))
2191+
defer ts.Close()
2192+
2193+
client := New().
2194+
SetTimeout(5 * time.Second).
2195+
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
2196+
EnableTrace()
2197+
2198+
resp, err := client.R().Get(ts.URL)
2199+
assertNil(t, err)
2200+
assertNotNil(t, resp)
2201+
2202+
tr := resp.Request.TraceInfo()
2203+
2204+
assertEqual(t, true, tr.TLSHandshake > 0)
2205+
assertEqual(t, true, tr.DNSLookup >= 0)
2206+
assertEqual(t, true, tr.ConnTime >= 0)
2207+
assertEqual(t, true, tr.TCPConnTime >= 0)
2208+
assertEqual(t, true, tr.ServerTime >= 0)
2209+
assertEqual(t, true, tr.ResponseTime >= 0)
2210+
assertEqual(t, true, tr.TotalTime > 0)
2211+
assertEqual(t, true, tr.TotalTime == resp.Time())
2212+
})
2213+
}
2214+
21382215
func TestDebugLoggerRequestBodyTooLarge(t *testing.T) {
21392216
ts := createFilePostServer(t)
21402217
defer ts.Close()

trace.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"crypto/tls"
1010
"net"
1111
"net/http/httptrace"
12+
"sync"
1213
"time"
1314
)
1415

@@ -70,6 +71,7 @@ type TraceInfo struct {
7071
// with the same naming for easy understanding. Plus additional insights
7172
// [Request].
7273
type clientTrace struct {
74+
lock sync.RWMutex
7375
getConn time.Time
7476
dnsStart time.Time
7577
dnsDone time.Time
@@ -87,37 +89,55 @@ func (t *clientTrace) createContext(ctx context.Context) context.Context {
8789
ctx,
8890
&httptrace.ClientTrace{
8991
DNSStart: func(_ httptrace.DNSStartInfo) {
92+
t.lock.Lock()
9093
t.dnsStart = time.Now()
94+
t.lock.Unlock()
9195
},
9296
DNSDone: func(_ httptrace.DNSDoneInfo) {
97+
t.lock.Lock()
9398
t.dnsDone = time.Now()
99+
t.lock.Unlock()
94100
},
95101
ConnectStart: func(_, _ string) {
102+
t.lock.Lock()
96103
if t.dnsDone.IsZero() {
97104
t.dnsDone = time.Now()
98105
}
99106
if t.dnsStart.IsZero() {
100107
t.dnsStart = t.dnsDone
101108
}
109+
t.lock.Unlock()
102110
},
103111
ConnectDone: func(net, addr string, err error) {
112+
t.lock.Lock()
104113
t.connectDone = time.Now()
114+
t.lock.Unlock()
105115
},
106116
GetConn: func(_ string) {
117+
t.lock.Lock()
107118
t.getConn = time.Now()
119+
t.lock.Unlock()
108120
},
109121
GotConn: func(ci httptrace.GotConnInfo) {
122+
t.lock.Lock()
110123
t.gotConn = time.Now()
111124
t.gotConnInfo = ci
125+
t.lock.Unlock()
112126
},
113127
GotFirstResponseByte: func() {
128+
t.lock.Lock()
114129
t.gotFirstResponseByte = time.Now()
130+
t.lock.Unlock()
115131
},
116132
TLSHandshakeStart: func() {
133+
t.lock.Lock()
117134
t.tlsHandshakeStart = time.Now()
135+
t.lock.Unlock()
118136
},
119137
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
138+
t.lock.Lock()
120139
t.tlsHandshakeDone = time.Now()
140+
t.lock.Unlock()
121141
},
122142
},
123143
)

0 commit comments

Comments
 (0)