diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 7421091ea..659f24c6b 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -357,7 +357,7 @@ private void CheckAndCaptureBaggage() /// /// Internal dictionary to keep track of and look up dropped span stats. /// - private Dictionary _droppedSpanStatsMap; + private ConcurrentDictionary _droppedSpanStatsMap; private bool _isEnded; @@ -552,38 +552,35 @@ private Activity StartActivity(bool shouldRestartTrace) return activity; } + private readonly object _lock = new(); internal void UpdateDroppedSpanStats(string serviceTargetType, string serviceTargetName, string destinationServiceResource, Outcome outcome, double duration ) { + //lock the lazy initialization of the dictionary if (_droppedSpanStatsMap == null) { - _droppedSpanStatsMap = new Dictionary - { - { - new DroppedSpanStatsKey(serviceTargetType, serviceTargetName, outcome), - new DroppedSpanStats(serviceTargetType, serviceTargetName, destinationServiceResource, outcome, duration) - } - }; + lock (_lock) + _droppedSpanStatsMap ??= new ConcurrentDictionary(); } - else + + lock (_lock) { if (_droppedSpanStatsMap.Count >= 128) return; - - if (_droppedSpanStatsMap.TryGetValue(new DroppedSpanStatsKey(serviceTargetType, serviceTargetName, outcome), out var item)) - { - item.Duration ??= - new DroppedSpanStats.DroppedSpanDuration { Sum = new DroppedSpanStats.DroppedSpanDuration.DroppedSpanDurationSum() }; - - item.Duration.Count++; - item.Duration.Sum.UsRaw += duration; - } - else - { - _droppedSpanStatsMap.Add(new DroppedSpanStatsKey(serviceTargetType, serviceTargetName, outcome), - new DroppedSpanStats(serviceTargetType, serviceTargetName, destinationServiceResource, outcome, duration)); - } + //AddOrUpdate callbacks can run multiple times so still wrapping this in a lock + var key = new DroppedSpanStatsKey(serviceTargetType, serviceTargetName, outcome); + _droppedSpanStatsMap.AddOrUpdate(key, + _ => new DroppedSpanStats(serviceTargetType, serviceTargetName, destinationServiceResource, outcome, duration), + (_, stats) => + { + stats.Duration ??= + new DroppedSpanStats.DroppedSpanDuration { Sum = new DroppedSpanStats.DroppedSpanDuration.DroppedSpanDurationSum() }; + + stats.Duration.Count++; + stats.Duration.Sum.UsRaw += duration; + return stats; + }); } }