diff --git a/ddtrace/tracer/rules_sampler.go b/ddtrace/tracer/rules_sampler.go index d0b61f8454..2cd911e3f7 100644 --- a/ddtrace/tracer/rules_sampler.go +++ b/ddtrace/tracer/rules_sampler.go @@ -498,20 +498,23 @@ func (rs *traceRulesSampler) sampleRules(span *span) bool { } func (rs *traceRulesSampler) applyRate(span *span, rate float64, now time.Time, sampler samplernames.SamplerName) { + span.Lock() + defer span.Unlock() + span.setMetric(keyRulesSamplerAppliedRate, rate) delete(span.Metrics, keySamplingPriorityRate) if !sampledByRate(span.TraceID, rate) { - span.setSamplingPriority(ext.PriorityUserReject, sampler) + span.setSamplingPriorityLocked(ext.PriorityUserReject, sampler) return } sampled, rate := rs.limiter.allowOne(now) if sampled { - span.setSamplingPriority(ext.PriorityUserKeep, sampler) + span.setSamplingPriorityLocked(ext.PriorityUserKeep, sampler) } else { - span.setSamplingPriority(ext.PriorityUserReject, sampler) + span.setSamplingPriorityLocked(ext.PriorityUserReject, sampler) } - span.SetTag(keyRulesSamplerLimiterRate, rate) + span.setMetric(keyRulesSamplerLimiterRate, rate) } // limit returns the rate limit set in the rules sampler, controlled by DD_TRACE_RATE_LIMIT, and diff --git a/ddtrace/tracer/span_test.go b/ddtrace/tracer/span_test.go index 3aa012e1cf..fffdf7fb10 100644 --- a/ddtrace/tracer/span_test.go +++ b/ddtrace/tracer/span_test.go @@ -1076,3 +1076,30 @@ type stringer struct{} func (s *stringer) String() string { return "string" } + +// TestConcurrentSpanSetTag tests that setting tags concurrently on a span directly or +// not (through tracer.Inject when trace sampling rules are in place) does not cause +// concurrent map writes. It seems to only be consistently reproduced with the -count=100 +// flag when running go test, but it's a good test to have. +func TestConcurrentSpanSetTag(t *testing.T) { + tracer, _, _, stop := startTestTracer(t, WithSamplingRules([]SamplingRule{NameRule("root", 1.0)})) + defer stop() + + span := tracer.StartSpan("root") + defer span.Finish() + + const n = 100 + wg := sync.WaitGroup{} + wg.Add(n * 2) + for i := 0; i < n; i++ { + go func() { + span.SetTag("key", "value") + wg.Done() + }() + go func() { + tracer.Inject(span.Context(), TextMapCarrier(map[string]string{})) + wg.Done() + }() + } + wg.Wait() +}