From 61e0abceb9a7816b8f1f301aa1462351e8d46983 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 7 Oct 2026 16:20:51 +1300 Subject: [PATCH] perf: counting discarded telemetry for client reports no longer allocates ThreadsafeCounterDictionary.Add and Increment passed a new CounterItem to GetOrAdd, allocating 24 B on every call even when the counter already existed. Use the factory overload so the item is only created for a new key. Closes #5686 Co-Authored-By: Claude Opus 5.5 --- .../Internal/ThreadsafeCounterDictionary.cs | 4 ++-- .../ThreadsafeCounterDictionaryTests.cs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Internal/ThreadsafeCounterDictionary.cs b/src/Sentry/Internal/ThreadsafeCounterDictionary.cs index 0265fba7b6..0f10e6a244 100644 --- a/src/Sentry/Internal/ThreadsafeCounterDictionary.cs +++ b/src/Sentry/Internal/ThreadsafeCounterDictionary.cs @@ -24,13 +24,13 @@ private class CounterItem /// /// The key of the counter to increment. /// The amount to add to the counter. - public void Add(TKey key, int quantity) => _items.GetOrAdd(key, new CounterItem()).Add(quantity); + public void Add(TKey key, int quantity) => _items.GetOrAdd(key, static _ => new CounterItem()).Add(quantity); /// /// Atomically increments a counter based on the key provided, creating the counter if necessary. /// /// The key of the counter to increment. - public void Increment(TKey key) => _items.GetOrAdd(key, new CounterItem()).Increment(); + public void Increment(TKey key) => _items.GetOrAdd(key, static _ => new CounterItem()).Increment(); /// /// Gets a single counter's value while atomically resetting it to zero. diff --git a/test/Sentry.Tests/Internals/ThreadsafeCounterDictionaryTests.cs b/test/Sentry.Tests/Internals/ThreadsafeCounterDictionaryTests.cs index fec7004088..09735f9940 100644 --- a/test/Sentry.Tests/Internals/ThreadsafeCounterDictionaryTests.cs +++ b/test/Sentry.Tests/Internals/ThreadsafeCounterDictionaryTests.cs @@ -275,4 +275,23 @@ void ReadAndAddToGrandTotal() const int expectedGrandTotal = numCounters * numThreadsPerCounter * numIterationsPerThread; Assert.Equal(expectedGrandTotal, grandTotal); } + +#if NET5_0_OR_GREATER && !__MOBILE__ + [Fact] + public void AddAndIncrement_ExistingCounter_DoNotAllocate() + { + var key = DiscardReason.CallbackError.WithCategory(DataCategory.LogItem); + var counters = new ThreadsafeCounterDictionary(); + counters.Add(key, 1); + counters.Increment(key); + + var before = GC.GetAllocatedBytesForCurrentThread(); + counters.Add(key, 1); + counters.Increment(key); + var allocated = GC.GetAllocatedBytesForCurrentThread() - before; + + Assert.Equal(0, allocated); + Assert.Equal(4, counters[key]); + } +#endif }