diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7ea13693e..ec2947488c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Changed + +- `SpanFromContext` and `SpanContextFromContext` in `go.opentelemetry.io/otel/trace` no longer make a heap allocation when the passed context has no span. (#5049) + ### Fixed - Clarify the documentation about equivalence guarantees for the `Set` and `Distinct` types in `go.opentelemetry.io/otel/attribute`. (#5027) diff --git a/trace/context.go b/trace/context.go index 5074bb44557..5650a174b4a 100644 --- a/trace/context.go +++ b/trace/context.go @@ -36,12 +36,12 @@ func ContextWithRemoteSpanContext(parent context.Context, rsc SpanContext) conte // performs no operations is returned. func SpanFromContext(ctx context.Context) Span { if ctx == nil { - return noopSpan{} + return noopSpanInstance } if span, ok := ctx.Value(currentSpanKey).(Span); ok { return span } - return noopSpan{} + return noopSpanInstance } // SpanContextFromContext returns the current Span's SpanContext. diff --git a/trace/context_test.go b/trace/context_test.go index d9378e1c430..38deb0aee5f 100644 --- a/trace/context_test.go +++ b/trace/context_test.go @@ -77,6 +77,16 @@ func TestSpanFromContext(t *testing.T) { // Ensure SpanContextFromContext is just // SpanFromContext(…).SpanContext(). assert.Equal(t, tc.expectedSpan.SpanContext(), SpanContextFromContext(tc.context)) + + // Check that SpanFromContext does not produce any heap allocation. + assert.Equal(t, 0.0, testing.AllocsPerRun(5, func() { + SpanFromContext(tc.context) + }), "SpanFromContext allocs") + + // Check that SpanContextFromContext does not produce any heap allocation. + assert.Equal(t, 0.0, testing.AllocsPerRun(5, func() { + SpanContextFromContext(tc.context) + }), "SpanContextFromContext allocs") }) } } diff --git a/trace/noop.go b/trace/noop.go index 583f109b064..84c775492ba 100644 --- a/trace/noop.go +++ b/trace/noop.go @@ -41,7 +41,7 @@ func (t noopTracer) Start(ctx context.Context, name string, _ ...SpanStartOption span := SpanFromContext(ctx) if _, ok := span.(nonRecordingSpan); !ok { // span is likely already a noopSpan, but let's be sure - span = noopSpan{} + span = noopSpanInstance } return ContextWithSpan(ctx, span), span } @@ -49,7 +49,7 @@ func (t noopTracer) Start(ctx context.Context, name string, _ ...SpanStartOption // noopSpan is an implementation of Span that performs no operations. type noopSpan struct{ embedded.Span } -var _ Span = noopSpan{} +var noopSpanInstance Span = noopSpan{} // SpanContext returns an empty span context. func (noopSpan) SpanContext() SpanContext { return SpanContext{} }