diff --git a/HzMemoryCache/Diagnostics/HzActivities.cs b/HzMemoryCache/Diagnostics/HzActivities.cs index 19291bb..b1f0b4e 100644 --- a/HzMemoryCache/Diagnostics/HzActivities.cs +++ b/HzMemoryCache/Diagnostics/HzActivities.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; namespace HzCache.Diagnostics @@ -6,7 +6,8 @@ namespace HzCache.Diagnostics public static class HzActivities { public const string HzCacheActivitySourceName = "HzMemoryCache"; - public static ActivitySource? Source => new(HzCacheActivitySourceName); + private static readonly ActivitySource source = new(HzCacheActivitySourceName); + public static ActivitySource Source => source; public static class Names { diff --git a/UnitTests/DiagnosticsTests.cs b/UnitTests/DiagnosticsTests.cs new file mode 100644 index 0000000..2ccc4c3 --- /dev/null +++ b/UnitTests/DiagnosticsTests.cs @@ -0,0 +1,49 @@ +using System.Diagnostics; +using HzCache; +using HzCache.Diagnostics; + +namespace UnitTests +{ + [TestClass] + public class DiagnosticsTests + { + [TestMethod] + public void SourceIsASingleInstance() + { + Assert.AreSame(HzActivities.Source, HzActivities.Source); + } + + [TestMethod] + public async Task CacheOperationsDoNotCreateNewActivitySources() + { + // Every ActivitySource constructor registers the instance in the runtime's global list and + // invokes ShouldListenTo on all listeners, so any source with our name other than the one + // expected instance is a per-call construction. Touch Source first so the static instance + // exists before the listener is registered, regardless of test ordering. + var expected = HzActivities.Source; + var created = 0; + using var listener = new ActivityListener + { + ShouldListenTo = source => + { + if (source.Name == HzActivities.HzCacheActivitySourceName && !ReferenceEquals(source, expected)) + Interlocked.Increment(ref created); + return false; + } + }; + ActivitySource.AddActivityListener(listener); + + using var cache = new HzMemoryCache(new HzCacheOptions { cleanupJobInterval = 20, notificationType = NotificationType.Sync }); + for (var i = 0; i < 100; i++) + { + cache.GetOrSet($"key{i}", _ => new MockObject(i), TimeSpan.FromMinutes(1)); + cache.Get($"key{i}"); + } + cache.Remove("key0"); + cache.RemoveByPattern("key1*"); + await Task.Delay(200); // several cleanup-timer ticks + + Assert.AreEqual(0, created); + } + } +}