Skip to content

Commit bc76a44

Browse files
fix: negative trace substraction when using SetTimeout (#1038)
1 parent 4209efb commit bc76a44

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

request.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,16 +1241,31 @@ func (r *Request) TraceInfo() TraceInfo {
12411241
return TraceInfo{}
12421242
}
12431243

1244+
ct.lock.RLock()
1245+
defer ct.lock.RUnlock()
1246+
12441247
ti := TraceInfo{
1245-
DNSLookup: ct.dnsDone.Sub(ct.dnsStart),
1246-
TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart),
1247-
ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn),
1248+
DNSLookup: 0,
1249+
TCPConnTime: 0,
1250+
ServerTime: 0,
12481251
IsConnReused: ct.gotConnInfo.Reused,
12491252
IsConnWasIdle: ct.gotConnInfo.WasIdle,
12501253
ConnIdleTime: ct.gotConnInfo.IdleTime,
12511254
RequestAttempt: r.Attempt,
12521255
}
12531256

1257+
if !ct.dnsStart.IsZero() && !ct.dnsDone.IsZero() {
1258+
ti.DNSLookup = ct.dnsDone.Sub(ct.dnsStart)
1259+
}
1260+
1261+
if !ct.tlsHandshakeDone.IsZero() && !ct.tlsHandshakeStart.IsZero() {
1262+
ti.TLSHandshake = ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart)
1263+
}
1264+
1265+
if !ct.gotFirstResponseByte.IsZero() && !ct.gotConn.IsZero() {
1266+
ti.ServerTime = ct.gotFirstResponseByte.Sub(ct.gotConn)
1267+
}
1268+
12541269
// Calculate the total time accordingly when connection is reused,
12551270
// and DNS start and get conn time may be zero if the request is invalid.
12561271
// See issue #1016.

request_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,55 @@ func TestTraceInfoOnTimeout(t *testing.T) {
19271927
assertEqual(t, true, tr.TotalTime == resp.Duration())
19281928
}
19291929

1930+
func TestTraceInfoOnTimeoutWithSetTimeout(t *testing.T) {
1931+
t.Run("timeout with very short timeout", func(t *testing.T) {
1932+
client := New().
1933+
SetTimeout(1 * time.Millisecond).
1934+
SetBaseURL("http://resty-nowhere.local").
1935+
EnableTrace()
1936+
1937+
resp, err := client.R().Get("/")
1938+
assertNotNil(t, err)
1939+
assertNotNil(t, resp)
1940+
1941+
tr := resp.Request.TraceInfo()
1942+
1943+
assertEqual(t, true, tr.DNSLookup == 0)
1944+
assertEqual(t, true, tr.ConnTime == 0)
1945+
assertEqual(t, true, tr.TLSHandshake == 0)
1946+
assertEqual(t, true, tr.TCPConnTime == 0)
1947+
assertEqual(t, true, tr.ServerTime == 0)
1948+
assertEqual(t, true, tr.ResponseTime == 0)
1949+
assertEqual(t, true, tr.TotalTime > 0)
1950+
assertEqual(t, true, tr.TotalTime == resp.Duration())
1951+
})
1952+
1953+
t.Run("successful request with SetTimeout", func(t *testing.T) {
1954+
ts := createGetServer(t)
1955+
defer ts.Close()
1956+
1957+
client := New().
1958+
SetTimeout(5 * time.Second).
1959+
SetBaseURL(ts.URL).
1960+
EnableTrace()
1961+
1962+
resp, err := client.R().Get("/")
1963+
assertNil(t, err)
1964+
assertNotNil(t, resp)
1965+
1966+
tr := resp.Request.TraceInfo()
1967+
1968+
assertEqual(t, true, tr.DNSLookup >= 0)
1969+
assertEqual(t, true, tr.ConnTime >= 0)
1970+
assertEqual(t, true, tr.TLSHandshake >= 0)
1971+
assertEqual(t, true, tr.TCPConnTime >= 0)
1972+
assertEqual(t, true, tr.ServerTime >= 0)
1973+
assertEqual(t, true, tr.ResponseTime >= 0)
1974+
assertEqual(t, true, tr.TotalTime > 0)
1975+
assertEqual(t, true, tr.TotalTime == resp.Duration())
1976+
})
1977+
}
1978+
19301979
func TestDebugLoggerRequestBodyTooLarge(t *testing.T) {
19311980
formTs := createFormPostServer(t)
19321981
defer formTs.Close()

trace.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"crypto/tls"
1111
"fmt"
1212
"net/http/httptrace"
13+
"sync"
1314
"time"
1415
)
1516

@@ -95,6 +96,7 @@ func (ti TraceInfo) Clone() *TraceInfo {
9596
// with the same naming for easy understanding. Plus additional insights
9697
// [Request].
9798
type clientTrace struct {
99+
lock sync.RWMutex
98100
getConn time.Time
99101
dnsStart time.Time
100102
dnsDone time.Time
@@ -112,37 +114,55 @@ func (t *clientTrace) createContext(ctx context.Context) context.Context {
112114
ctx,
113115
&httptrace.ClientTrace{
114116
DNSStart: func(_ httptrace.DNSStartInfo) {
117+
t.lock.Lock()
115118
t.dnsStart = time.Now()
119+
t.lock.Unlock()
116120
},
117121
DNSDone: func(_ httptrace.DNSDoneInfo) {
122+
t.lock.Lock()
118123
t.dnsDone = time.Now()
124+
t.lock.Unlock()
119125
},
120126
ConnectStart: func(_, _ string) {
127+
t.lock.Lock()
121128
if t.dnsDone.IsZero() {
122129
t.dnsDone = time.Now()
123130
}
124131
if t.dnsStart.IsZero() {
125132
t.dnsStart = t.dnsDone
126133
}
134+
t.lock.Unlock()
127135
},
128136
ConnectDone: func(net, addr string, err error) {
137+
t.lock.Lock()
129138
t.connectDone = time.Now()
139+
t.lock.Unlock()
130140
},
131141
GetConn: func(_ string) {
142+
t.lock.Lock()
132143
t.getConn = time.Now()
144+
t.lock.Unlock()
133145
},
134146
GotConn: func(ci httptrace.GotConnInfo) {
147+
t.lock.Lock()
135148
t.gotConn = time.Now()
136149
t.gotConnInfo = ci
150+
t.lock.Unlock()
137151
},
138152
GotFirstResponseByte: func() {
153+
t.lock.Lock()
139154
t.gotFirstResponseByte = time.Now()
155+
t.lock.Unlock()
140156
},
141157
TLSHandshakeStart: func() {
158+
t.lock.Lock()
142159
t.tlsHandshakeStart = time.Now()
160+
t.lock.Unlock()
143161
},
144162
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
163+
t.lock.Lock()
145164
t.tlsHandshakeDone = time.Now()
165+
t.lock.Unlock()
146166
},
147167
},
148168
)

0 commit comments

Comments
 (0)