Skip to content

Commit 9d69608

Browse files
authored
Comment unsafe memory usage in ingester push path (#2004)
* Wrap ingester Push errors to avoid retaining any reference to unsafe data * Comment unsafe memory usage in ingester push path Signed-off-by: Bryan Boreham <[email protected]>
1 parent c0db39b commit 9d69608

File tree

5 files changed

+32
-16
lines changed

5 files changed

+32
-16
lines changed

pkg/ingester/errors.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"net/http"
66

7-
"github.com/pkg/errors"
87
"github.com/prometheus/prometheus/pkg/labels"
98
"github.com/weaveworks/common/httpgrpc"
109
)
@@ -50,11 +49,6 @@ func makeMetricLimitError(errorType string, labels labels.Labels, err error) err
5049
}
5150
}
5251

53-
func (e *validationError) WrapWithUser(userID string) *validationError {
54-
e.err = wrapWithUser(e.err, userID)
55-
return e
56-
}
57-
5852
func (e *validationError) Error() string {
5953
if e.err == nil {
6054
return e.errorType
@@ -65,14 +59,15 @@ func (e *validationError) Error() string {
6559
return fmt.Sprintf("%s for series %s", e.err.Error(), e.labels.String())
6660
}
6761

68-
// WrappedError returns a HTTP gRPC error than is correctly forwarded over gRPC.
69-
func (e *validationError) WrappedError() error {
62+
// returns a HTTP gRPC error than is correctly forwarded over gRPC, with no reference to `e` retained.
63+
func grpcForwardableError(userID string, code int, e error) error {
7064
return httpgrpc.ErrorFromHTTPResponse(&httpgrpc.HTTPResponse{
71-
Code: int32(e.code),
72-
Body: []byte(e.Error()),
65+
Code: int32(code),
66+
Body: []byte(wrapWithUser(e, userID).Error()),
7367
})
7468
}
7569

70+
// Note: does not retain a reference to `err`
7671
func wrapWithUser(err error, userID string) error {
77-
return errors.Wrapf(err, "user=%s", userID)
72+
return fmt.Errorf("user=%s: %s", userID, err)
7873
}

pkg/ingester/index/index.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ func New() *InvertedIndex {
3232
}
3333

3434
// Add a fingerprint under the specified labels.
35+
// NOTE: memory for `labels` is unsafe; anything retained beyond the
36+
// life of this function must be copied
3537
func (ii *InvertedIndex) Add(labels []client.LabelAdapter, fp model.Fingerprint) labels.Labels {
3638
shard := &ii.shards[util.HashFP(fp)%indexShards]
37-
return shard.add(labels, fp)
39+
return shard.add(labels, fp) // add() returns 'interned' values so the original labels are not retained
3840
}
3941

4042
// Lookup all fingerprints for the provided matchers.
@@ -109,7 +111,9 @@ func copyString(s string) string {
109111
return string([]byte(s))
110112
}
111113

112-
// add metric to the index; return all the name/value pairs as strings from the index, sorted
114+
// add metric to the index; return all the name/value pairs as a fresh
115+
// sorted slice, referencing 'interned' strings from the index so that
116+
// no references are retained to the memory of `metric`.
113117
func (shard *indexShard) add(metric []client.LabelAdapter, fp model.Fingerprint) labels.Labels {
114118
shard.mtx.Lock()
115119
defer shard.mtx.Unlock()

pkg/ingester/ingester.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ func (i *Ingester) Push(ctx context.Context, req *client.WriteRequest) (*client.
274274
return i.v2Push(ctx, req)
275275
}
276276

277+
// NOTE: because we use `unsafe` in deserialisation, we must not
278+
// retain anything from `req` past the call to ReuseSlice
277279
defer client.ReuseSlice(req.Timeseries)
278280

279281
userID, err := user.ExtractOrgID(ctx)
@@ -298,6 +300,7 @@ func (i *Ingester) Push(ctx context.Context, req *client.WriteRequest) (*client.
298300

299301
for _, ts := range req.Timeseries {
300302
for _, s := range ts.Samples {
303+
// append() copies the memory in `ts.Labels` except on the error path
301304
err := i.append(ctx, userID, ts.Labels, model.Time(s.TimestampMs), model.SampleValue(s.Value), req.Source, record)
302305
if err == nil {
303306
continue
@@ -309,12 +312,14 @@ func (i *Ingester) Push(ctx context.Context, req *client.WriteRequest) (*client.
309312
continue
310313
}
311314

312-
return nil, wrapWithUser(err, userID)
315+
// non-validation error: abandon this request
316+
return nil, grpcForwardableError(userID, http.StatusInternalServerError, err)
313317
}
314318
}
315319

316320
if lastPartialErr != nil {
317-
return &client.WriteResponse{}, lastPartialErr.WrapWithUser(userID).WrappedError()
321+
// grpcForwardableError turns the error into a string so it no longer references `req`
322+
return &client.WriteResponse{}, grpcForwardableError(userID, lastPartialErr.code, lastPartialErr)
318323
}
319324

320325
if record != nil {
@@ -328,6 +333,8 @@ func (i *Ingester) Push(ctx context.Context, req *client.WriteRequest) (*client.
328333
return &client.WriteResponse{}, nil
329334
}
330335

336+
// NOTE: memory for `labels` is unsafe; anything retained beyond the
337+
// life of this function must be copied
331338
func (i *Ingester) append(ctx context.Context, userID string, labels labelPairs, timestamp model.Time, value model.SampleValue, source client.WriteRequest_SourceEnum, record *Record) error {
332339
labels.removeBlanks()
333340

@@ -346,6 +353,7 @@ func (i *Ingester) append(ctx context.Context, userID string, labels labelPairs,
346353
return fmt.Errorf("ingester stopping")
347354
}
348355

356+
// getOrCreateSeries copies the memory for `labels`, except on the error path.
349357
state, fp, series, err := i.userStates.getOrCreateSeries(ctx, userID, labels, record)
350358
if err != nil {
351359
if ve, ok := err.(*validationError); ok {

pkg/ingester/ingester_v2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ func (i *Ingester) updateLoop() {
188188
func (i *Ingester) v2Push(ctx context.Context, req *client.WriteRequest) (*client.WriteResponse, error) {
189189
var firstPartialErr error
190190

191+
// NOTE: because we use `unsafe` in deserialisation, we must not
192+
// retain anything from `req` past the call to ReuseSlice
191193
defer client.ReuseSlice(req.Timeseries)
192194

193195
userID, err := user.ExtractOrgID(ctx)

pkg/ingester/user_state.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,17 @@ func (us *userStates) getViaContext(ctx context.Context) (*userState, bool, erro
156156
return state, ok, nil
157157
}
158158

159+
// NOTE: memory for `labels` is unsafe; anything retained beyond the
160+
// life of this function must be copied
159161
func (us *userStates) getOrCreateSeries(ctx context.Context, userID string, labels []client.LabelAdapter, record *Record) (*userState, model.Fingerprint, *memorySeries, error) {
160162
state := us.getOrCreate(userID)
163+
// WARNING: `err` may have a reference to unsafe memory in `labels`
161164
fp, series, err := state.getSeries(labels, record)
162165
return state, fp, series, err
163166
}
164167

168+
// NOTE: memory for `metric` is unsafe; anything retained beyond the
169+
// life of this function must be copied
165170
func (u *userState) getSeries(metric labelPairs, record *Record) (model.Fingerprint, *memorySeries, error) {
166171
rawFP := client.FastFingerprint(metric)
167172
u.fpLocker.Lock(rawFP)
@@ -198,6 +203,7 @@ func (u *userState) createSeriesWithFingerprint(fp model.Fingerprint, metric lab
198203
}
199204
}
200205

206+
// MetricNameFromLabelAdapters returns a copy of the string in `metric`
201207
metricName, err := extract.MetricNameFromLabelAdapters(metric)
202208
if err != nil {
203209
return nil, err
@@ -206,6 +212,7 @@ func (u *userState) createSeriesWithFingerprint(fp model.Fingerprint, metric lab
206212
if !recovery {
207213
// Check if the per-metric limit has been exceeded
208214
if err = u.canAddSeriesFor(string(metricName)); err != nil {
215+
// WARNING: returns a reference to `metric`
209216
return nil, makeMetricLimitError(perMetricSeriesLimit, client.FromLabelAdaptersToLabels(metric), err)
210217
}
211218
}
@@ -220,7 +227,7 @@ func (u *userState) createSeriesWithFingerprint(fp model.Fingerprint, metric lab
220227
})
221228
}
222229

223-
labels := u.index.Add(metric, fp)
230+
labels := u.index.Add(metric, fp) // Add() returns 'interned' values so the original labels are not retained
224231
series := newMemorySeries(labels)
225232
u.fpToSeries.put(fp, series)
226233

0 commit comments

Comments
 (0)