Skip to content

Commit 5fee405

Browse files
committed
transport/http: fix metrics race condition
This PR fixes a race condition reported by #548, by introducing a simple wrapper for time.Time. While by default aws-sdk-go-v2 uses NopMeterProvider, the transport/http internals are still instrumenting the http requests (the withMetrics func). Maybe there is a room for improvement, to not call httptrace.WithClientTrace when the meter is nop, however this is out of scope for this PR.
1 parent a7d0f1e commit 5fee405

File tree

2 files changed

+38
-15
lines changed

2 files changed

+38
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "c898d340-9c15-4b9a-a5da-93a340a9d5f8",
3+
"type": "bugfix",
4+
"collapse": true,
5+
"description": "transport/http: fix metrics race condition",
6+
"modules": [
7+
"transport/http"
8+
]
9+
}

transport/http/metrics.go

+29-15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/tls"
66
"net/http"
77
"net/http/httptrace"
8+
"sync/atomic"
89
"time"
910

1011
"github.com/aws/smithy-go/metrics"
@@ -42,10 +43,10 @@ type timedClientDo struct {
4243
}
4344

4445
func (c *timedClientDo) Do(r *http.Request) (*http.Response, error) {
45-
c.hm.doStart = now()
46+
c.hm.doStart.Store(now())
4647
resp, err := c.ClientDo.Do(r)
4748

48-
c.hm.DoRequestDuration.Record(r.Context(), elapsed(c.hm.doStart))
49+
c.hm.DoRequestDuration.Record(r.Context(), c.hm.doStart.Elapsed())
4950
return resp, err
5051
}
5152

@@ -58,10 +59,10 @@ type httpMetrics struct {
5859
DoRequestDuration metrics.Float64Histogram // client.http.do_request_duration
5960
TimeToFirstByte metrics.Float64Histogram // client.http.time_to_first_byte
6061

61-
doStart time.Time
62-
dnsStart time.Time
63-
connectStart time.Time
64-
tlsStart time.Time
62+
doStart safeTime
63+
dnsStart safeTime
64+
connectStart safeTime
65+
tlsStart safeTime
6566
}
6667

6768
func newHTTPMetrics(meter metrics.Meter) (*httpMetrics, error) {
@@ -115,15 +116,15 @@ func newHTTPMetrics(meter metrics.Meter) (*httpMetrics, error) {
115116
}
116117

117118
func (m *httpMetrics) DNSStart(httptrace.DNSStartInfo) {
118-
m.dnsStart = now()
119+
m.dnsStart.Store(now())
119120
}
120121

121122
func (m *httpMetrics) ConnectStart(string, string) {
122-
m.connectStart = now()
123+
m.connectStart.Store(now())
123124
}
124125

125126
func (m *httpMetrics) TLSHandshakeStart() {
126-
m.tlsStart = now()
127+
m.tlsStart.Store(now())
127128
}
128129

129130
func (m *httpMetrics) GotConn(ctx context.Context) func(httptrace.GotConnInfo) {
@@ -140,25 +141,25 @@ func (m *httpMetrics) PutIdleConn(ctx context.Context) func(error) {
140141

141142
func (m *httpMetrics) DNSDone(ctx context.Context) func(httptrace.DNSDoneInfo) {
142143
return func(httptrace.DNSDoneInfo) {
143-
m.DNSLookupDuration.Record(ctx, elapsed(m.dnsStart))
144+
m.DNSLookupDuration.Record(ctx, m.dnsStart.Elapsed())
144145
}
145146
}
146147

147148
func (m *httpMetrics) ConnectDone(ctx context.Context) func(string, string, error) {
148149
return func(string, string, error) {
149-
m.ConnectDuration.Record(ctx, elapsed(m.connectStart))
150+
m.ConnectDuration.Record(ctx, m.connectStart.Elapsed())
150151
}
151152
}
152153

153154
func (m *httpMetrics) TLSHandshakeDone(ctx context.Context) func(tls.ConnectionState, error) {
154155
return func(tls.ConnectionState, error) {
155-
m.TLSHandshakeDuration.Record(ctx, elapsed(m.tlsStart))
156+
m.TLSHandshakeDuration.Record(ctx, m.tlsStart.Elapsed())
156157
}
157158
}
158159

159160
func (m *httpMetrics) GotFirstResponseByte(ctx context.Context) func() {
160161
return func() {
161-
m.TimeToFirstByte.Record(ctx, elapsed(m.doStart))
162+
m.TimeToFirstByte.Record(ctx, m.doStart.Elapsed())
162163
}
163164
}
164165

@@ -177,8 +178,21 @@ func (m *httpMetrics) addConnIdle(ctx context.Context, incr int64) {
177178
})
178179
}
179180

180-
func elapsed(start time.Time) float64 {
181+
type safeTime struct {
182+
atomic.Value // time.Time
183+
}
184+
185+
func (st *safeTime) Store(v time.Time) {
186+
st.Value.Store(v)
187+
}
188+
189+
func (st *safeTime) Load() time.Time {
190+
t, _ := st.Value.Load().(time.Time)
191+
return t
192+
}
193+
194+
func (st *safeTime) Elapsed() float64 {
181195
end := now()
182-
elapsed := end.Sub(start)
196+
elapsed := end.Sub(st.Load())
183197
return float64(elapsed) / 1e9
184198
}

0 commit comments

Comments
 (0)