From a4d2607b711732bc73b97f35d93c223c8e3a18fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Sun, 10 Aug 2025 19:51:39 +0200 Subject: [PATCH 01/13] perf: remove NSubstitute from benchmark for Structured Logs --- .../StructuredLogBatchProcessorBenchmarks.cs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs b/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs index 336d726926..672d453876 100644 --- a/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs @@ -1,5 +1,4 @@ using BenchmarkDotNet.Attributes; -using NSubstitute; using Sentry.Extensibility; using Sentry.Internal; @@ -30,22 +29,10 @@ public void Setup() }; var batchInterval = Timeout.InfiniteTimeSpan; - - var clientReportRecorder = Substitute.For(); - clientReportRecorder - .When(static recorder => recorder.RecordDiscardedEvent(Arg.Any(), Arg.Any(), Arg.Any())) - .Throw(); - - var diagnosticLogger = Substitute.For(); - diagnosticLogger - .When(static logger => logger.IsEnabled(Arg.Any())) - .Throw(); - diagnosticLogger - .When(static logger => logger.Log(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())) - .Throw(); + var clientReportRecorder = new NullClientReportRecorder(); _hub = new Hub(options, DisabledHub.Instance); - _batchProcessor = new StructuredLogBatchProcessor(_hub, BatchCount, batchInterval, clientReportRecorder, diagnosticLogger); + _batchProcessor = new StructuredLogBatchProcessor(_hub, BatchCount, batchInterval, clientReportRecorder, null); _log = new SentryLog(DateTimeOffset.Now, SentryId.Empty, SentryLogLevel.Trace, "message"); } @@ -66,3 +53,21 @@ public void Cleanup() _hub.Dispose(); } } + +file sealed class NullClientReportRecorder : IClientReportRecorder +{ + public void RecordDiscardedEvent(DiscardReason reason, DataCategory category, int quantity = 1) + { + // no-op + } + + public ClientReport GenerateClientReport() + { + throw new UnreachableException(); + } + + public void Load(ClientReport report) + { + throw new UnreachableException(); + } +} From 17bbed7ed2be144ad00271fc1fa3daaccdc8efed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Sun, 10 Aug 2025 20:06:36 +0200 Subject: [PATCH 02/13] fix(logs): race condition when flushing Batch-Buffer --- src/Sentry/Threading/ScopedCountdownLock.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index bc3725f1a9..68630f05b9 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -84,13 +84,12 @@ internal LockScope TryEnterLockScope() private void ExitLockScope() { - if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) == 1) + _event.Reset(); // reset the signaled event to the initial count of 1, so that new `CounterScope`s can be entered again + + if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) != 1) { - _event.Reset(); // reset the signaled event to the initial count of 1, so that new `CounterScope`s can be entered again - return; + Debug.Fail("The Lock should have not been disengaged without being engaged first."); } - - Debug.Fail("The Lock should have not been disengaged without being engaged first."); } /// From 089fffc5772007da7eb2d734d89a2b3c43e062cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Sun, 10 Aug 2025 20:08:13 +0200 Subject: [PATCH 03/13] ref(logs): pass arguments explicitly to Wait method --- src/Sentry/Threading/ScopedCountdownLock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index 68630f05b9..539df66258 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -138,7 +138,7 @@ internal LockScope(ScopedCountdownLock lockObj) internal void Wait() { var lockObj = _lockObj; - lockObj?._event.Wait(); + lockObj?._event.Wait(Timeout.Infinite, CancellationToken.None); } public void Dispose() From 34c992f85c85190acfa3fe3bedf263a881560bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Sun, 10 Aug 2025 20:17:14 +0200 Subject: [PATCH 04/13] docs(logs): more precise comments --- src/Sentry/Internal/StructuredLogBatchBuffer.cs | 2 ++ src/Sentry/Threading/ScopedCountdownLock.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Internal/StructuredLogBatchBuffer.cs b/src/Sentry/Internal/StructuredLogBatchBuffer.cs index 7e518253a6..a9f49216b7 100644 --- a/src/Sentry/Internal/StructuredLogBatchBuffer.cs +++ b/src/Sentry/Internal/StructuredLogBatchBuffer.cs @@ -8,6 +8,8 @@ namespace Sentry.Internal; /// /// Must be attempted to flush via when either the is reached, /// or when the is exceeded. +/// Utilizes a , basically used as an inverse , +/// allowing multiple threads for or exclusive access for . /// [DebuggerDisplay("Name = {Name}, Capacity = {Capacity}, Additions = {_additions}, AddCount = {AddCount}, IsDisposed = {_disposed}")] internal sealed class StructuredLogBatchBuffer : IDisposable diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index 539df66258..6939040338 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -75,7 +75,7 @@ internal LockScope TryEnterLockScope() { if (Interlocked.CompareExchange(ref _isEngaged, 1, 0) == 0) { - _ = _event.Signal(); // decrement the initial count of 1, so that the event can be set with the count reaching 0 when all 'CounterScope's have exited + _ = _event.Signal(); // decrement the initial count of 1, so that the event can be set with the count reaching 0 when all entered 'CounterScope' instances have exited return new LockScope(this); } @@ -84,7 +84,7 @@ internal LockScope TryEnterLockScope() private void ExitLockScope() { - _event.Reset(); // reset the signaled event to the initial count of 1, so that new `CounterScope`s can be entered again + _event.Reset(); // reset the signaled event to the initial count of 1, so that new 'CounterScope' instances can be entered again if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) != 1) { From 3608899f32e20743dff2abcb1ed1be39048461ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Sun, 10 Aug 2025 20:44:05 +0200 Subject: [PATCH 05/13] ref(logs): more Debug.Assert --- src/Sentry/Threading/ScopedCountdownLock.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index 6939040338..54af00afec 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -59,6 +59,7 @@ internal CounterScope TryEnterCounterScope() private void ExitCounterScope() { + Debug.Assert(_event.CurrentCount >= 1); _ = _event.Signal(); } @@ -75,6 +76,7 @@ internal LockScope TryEnterLockScope() { if (Interlocked.CompareExchange(ref _isEngaged, 1, 0) == 0) { + Debug.Assert(_event.CurrentCount >= 1); _ = _event.Signal(); // decrement the initial count of 1, so that the event can be set with the count reaching 0 when all entered 'CounterScope' instances have exited return new LockScope(this); } @@ -84,6 +86,7 @@ internal LockScope TryEnterLockScope() private void ExitLockScope() { + Debug.Assert(_event.IsSet); _event.Reset(); // reset the signaled event to the initial count of 1, so that new 'CounterScope' instances can be entered again if (Interlocked.CompareExchange(ref _isEngaged, 0, 1) != 1) From 0c75df7b74109324f7343c0f693953f0e6ec19b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:37:53 +0200 Subject: [PATCH 06/13] ref(logs): no longer enter Counter-Scopes when a Lock-Scope has been entered successfully --- src/Sentry/Threading/ScopedCountdownLock.cs | 11 ++++++++--- .../Threading/ScopedCountdownLockTests.cs | 14 +++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index 54af00afec..267d9dac02 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -31,16 +31,16 @@ internal ScopedCountdownLock() /// Gets the number of remaining required to exit in order to set/signal the event while a is active. /// When and while a is active, no more can be entered. /// - internal int Count => _isEngaged == 1 ? _event.CurrentCount : _event.CurrentCount - 1; + internal int Count => IsEngaged ? _event.CurrentCount : _event.CurrentCount - 1; /// /// Returns when a is active and the event can be set/signaled by reaching . /// Returns when the can only reach the initial count of when no is active any longer. /// - internal bool IsEngaged => _isEngaged == 1; + internal bool IsEngaged => Interlocked.CompareExchange(ref _isEngaged, 1, 1) == 1; /// - /// No will be entered when the has reached while the lock is engaged via an active . + /// No will be entered when the has reached , or while the lock is engaged via an active . /// Check via whether the underlying has not been set/signaled yet. /// To signal the underlying , ensure is called. /// @@ -49,6 +49,11 @@ internal ScopedCountdownLock() /// internal CounterScope TryEnterCounterScope() { + if (IsEngaged) + { + return new CounterScope(); + } + if (_event.TryAddCount(1)) { return new CounterScope(this); diff --git a/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs b/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs index dad07e1e23..d6bafc79a7 100644 --- a/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs +++ b/test/Sentry.Tests/Threading/ScopedCountdownLockTests.cs @@ -79,12 +79,12 @@ public void TryEnterLockScope_IsEngaged_IsSet() lockTwo.Dispose(); AssertEngaged(false, 1); - // successfully enter another CounterScope ... lock is engaged but not yet set + // cannot enter another CounterScope as long as the lock is already engaged by a LockScope var counterTwo = _lock.TryEnterCounterScope(); - counterTwo.IsEntered.Should().BeTrue(); - AssertEngaged(false, 2); + counterTwo.IsEntered.Should().BeFalse(); + AssertEngaged(false, 1); - // exit a CounterScope ... decrement the count + // no-op ... CounterScope is not entered counterTwo.Dispose(); AssertEngaged(false, 1); @@ -92,7 +92,7 @@ public void TryEnterLockScope_IsEngaged_IsSet() counterOne.Dispose(); AssertEngaged(true, 0); - // cannot enter another CounterScope as long as the engaged lock is set + // cannot enter another CounterScope as long as the lock is engaged and set var counterThree = _lock.TryEnterCounterScope(); counterThree.IsEntered.Should().BeFalse(); AssertEngaged(true, 0); @@ -102,11 +102,11 @@ public void TryEnterLockScope_IsEngaged_IsSet() // would block if the count of the engaged lock was not zero lockOne.Wait(); - // exit the LockScope ... reset the lock + // exit the LockScope ... reset and disengage the lock lockOne.Dispose(); AssertDisengaged(false, 0); - // can enter a CounterScope again ... the lock not set + // can enter a CounterScope again ... the lock is no longer set and no longer engaged var counterFour = _lock.TryEnterCounterScope(); counterFour.IsEntered.Should().BeTrue(); AssertDisengaged(false, 1); From 0b7c2486fcea1a0e397960a53e65e610dde6f658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:53:54 +0200 Subject: [PATCH 07/13] perf: add Parallel benchmark for Structured Logs --- ...gBatchProcessorBenchmarks-report-github.md | 22 ++++++++++++------- .../StructuredLogBatchProcessorBenchmarks.cs | 10 +++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md index befa791365..807a9de356 100644 --- a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md +++ b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md @@ -8,11 +8,17 @@ Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores ``` -| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Gen0 | Allocated | -|---------------- |----------- |-------------------- |------------:|---------:|---------:|-------:|----------:| -| **EnqueueAndFlush** | **10** | **100** | **1,774.5 ns** | **7.57 ns** | **6.71 ns** | **0.6104** | **5 KB** | -| **EnqueueAndFlush** | **10** | **200** | **3,468.5 ns** | **11.16 ns** | **10.44 ns** | **1.2207** | **10 KB** | -| **EnqueueAndFlush** | **10** | **1000** | **17,259.7 ns** | **51.92 ns** | **46.02 ns** | **6.1035** | **50 KB** | -| **EnqueueAndFlush** | **100** | **100** | **857.5 ns** | **4.21 ns** | **3.73 ns** | **0.1469** | **1.2 KB** | -| **EnqueueAndFlush** | **100** | **200** | **1,681.4 ns** | **1.74 ns** | **1.63 ns** | **0.2937** | **2.41 KB** | -| **EnqueueAndFlush** | **100** | **1000** | **8,302.2 ns** | **12.00 ns** | **10.64 ns** | **1.4648** | **12.03 KB** | +| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Median | Gen0 | Allocated | +|------------------------- |----------- |-------------------- |-----------:|----------:|----------:|-----------:|-------:|----------:| +| **EnqueueAndFlush** | **10** | **100** | **3.087 μs** | **0.0305 μs** | **0.0286 μs** | **3.077 μs** | **0.6104** | **5 KB** | +| EnqueueAndFlush_Parallel | 10 | 100 | 22.359 μs | 0.1047 μs | 0.0979 μs | 22.370 μs | 1.2207 | 9.98 KB | +| **EnqueueAndFlush** | **10** | **200** | **6.192 μs** | **0.0263 μs** | **0.0246 μs** | **6.188 μs** | **1.2207** | **10 KB** | +| EnqueueAndFlush_Parallel | 10 | 200 | 50.020 μs | 0.0814 μs | 0.0761 μs | 50.011 μs | 1.7090 | 13.92 KB | +| **EnqueueAndFlush** | **10** | **1000** | **29.180 μs** | **0.5809 μs** | **0.9044 μs** | **28.735 μs** | **6.1035** | **50 KB** | +| EnqueueAndFlush_Parallel | 10 | 1000 | 245.169 μs | 4.1653 μs | 3.8962 μs | 246.642 μs | 4.8828 | 43.35 KB | +| **EnqueueAndFlush** | **100** | **100** | **2.235 μs** | **0.0441 μs** | **0.1014 μs** | **2.262 μs** | **0.1450** | **1.2 KB** | +| EnqueueAndFlush_Parallel | 100 | 100 | 22.153 μs | 0.4426 μs | 0.9141 μs | 22.353 μs | 0.7019 | 5.86 KB | +| **EnqueueAndFlush** | **100** | **200** | **4.712 μs** | **0.0878 μs** | **0.0821 μs** | **4.678 μs** | **0.2899** | **2.41 KB** | +| EnqueueAndFlush_Parallel | 100 | 200 | 52.853 μs | 1.0549 μs | 2.2020 μs | 53.331 μs | 0.9155 | 7.52 KB | +| **EnqueueAndFlush** | **100** | **1000** | **22.633 μs** | **0.4470 μs** | **0.4390 μs** | **22.302 μs** | **1.4648** | **12.03 KB** | +| EnqueueAndFlush_Parallel | 100 | 1000 | 337.335 μs | 3.8933 μs | 3.6418 μs | 337.324 μs | 2.4414 | 20.71 KB | diff --git a/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs b/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs index 672d453876..9085068cce 100644 --- a/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/StructuredLogBatchProcessorBenchmarks.cs @@ -46,6 +46,16 @@ public void EnqueueAndFlush() _batchProcessor.Flush(); } + [Benchmark] + public void EnqueueAndFlush_Parallel() + { + _ = Parallel.For(0, OperationsPerInvoke, (int i) => + { + _batchProcessor.Enqueue(_log); + }); + _batchProcessor.Flush(); + } + [GlobalCleanup] public void Cleanup() { From 19ba13a29b7cb876498488a7560988d4e16ddee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:40:58 +0200 Subject: [PATCH 08/13] perf(logs): restore previous performance characteristics --- ...gBatchProcessorBenchmarks-report-github.md | 28 +++++++++---------- src/Sentry/Threading/ScopedCountdownLock.cs | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md index 807a9de356..5559d11f27 100644 --- a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md +++ b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md @@ -8,17 +8,17 @@ Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores ``` -| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Median | Gen0 | Allocated | -|------------------------- |----------- |-------------------- |-----------:|----------:|----------:|-----------:|-------:|----------:| -| **EnqueueAndFlush** | **10** | **100** | **3.087 μs** | **0.0305 μs** | **0.0286 μs** | **3.077 μs** | **0.6104** | **5 KB** | -| EnqueueAndFlush_Parallel | 10 | 100 | 22.359 μs | 0.1047 μs | 0.0979 μs | 22.370 μs | 1.2207 | 9.98 KB | -| **EnqueueAndFlush** | **10** | **200** | **6.192 μs** | **0.0263 μs** | **0.0246 μs** | **6.188 μs** | **1.2207** | **10 KB** | -| EnqueueAndFlush_Parallel | 10 | 200 | 50.020 μs | 0.0814 μs | 0.0761 μs | 50.011 μs | 1.7090 | 13.92 KB | -| **EnqueueAndFlush** | **10** | **1000** | **29.180 μs** | **0.5809 μs** | **0.9044 μs** | **28.735 μs** | **6.1035** | **50 KB** | -| EnqueueAndFlush_Parallel | 10 | 1000 | 245.169 μs | 4.1653 μs | 3.8962 μs | 246.642 μs | 4.8828 | 43.35 KB | -| **EnqueueAndFlush** | **100** | **100** | **2.235 μs** | **0.0441 μs** | **0.1014 μs** | **2.262 μs** | **0.1450** | **1.2 KB** | -| EnqueueAndFlush_Parallel | 100 | 100 | 22.153 μs | 0.4426 μs | 0.9141 μs | 22.353 μs | 0.7019 | 5.86 KB | -| **EnqueueAndFlush** | **100** | **200** | **4.712 μs** | **0.0878 μs** | **0.0821 μs** | **4.678 μs** | **0.2899** | **2.41 KB** | -| EnqueueAndFlush_Parallel | 100 | 200 | 52.853 μs | 1.0549 μs | 2.2020 μs | 53.331 μs | 0.9155 | 7.52 KB | -| **EnqueueAndFlush** | **100** | **1000** | **22.633 μs** | **0.4470 μs** | **0.4390 μs** | **22.302 μs** | **1.4648** | **12.03 KB** | -| EnqueueAndFlush_Parallel | 100 | 1000 | 337.335 μs | 3.8933 μs | 3.6418 μs | 337.324 μs | 2.4414 | 20.71 KB | +| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Median | Gen0 | Allocated | +|------------------------- |----------- |-------------------- |-------------:|------------:|------------:|-------------:|-------:|----------:| +| **EnqueueAndFlush** | **10** | **100** | **1,896.0 ns** | **6.92 ns** | **6.13 ns** | **1,895.0 ns** | **0.6104** | **5 KB** | +| EnqueueAndFlush_Parallel | 10 | 100 | 18,372.7 ns | 362.04 ns | 731.34 ns | 18,432.1 ns | 1.1292 | 9.21 KB | +| **EnqueueAndFlush** | **10** | **200** | **3,683.3 ns** | **11.97 ns** | **10.61 ns** | **3,682.6 ns** | **1.2207** | **10 KB** | +| EnqueueAndFlush_Parallel | 10 | 200 | 41,416.1 ns | 814.20 ns | 1,360.35 ns | 40,730.6 ns | 1.7090 | 14.01 KB | +| **EnqueueAndFlush** | **10** | **1000** | **17,336.4 ns** | **80.76 ns** | **75.54 ns** | **17,324.4 ns** | **6.1035** | **50 KB** | +| EnqueueAndFlush_Parallel | 10 | 1000 | 188,962.0 ns | 1,311.63 ns | 1,226.90 ns | 189,209.6 ns | 4.3945 | 36.6 KB | +| **EnqueueAndFlush** | **100** | **100** | **863.0 ns** | **0.88 ns** | **0.73 ns** | **862.8 ns** | **0.1469** | **1.2 KB** | +| EnqueueAndFlush_Parallel | 100 | 100 | 6,898.0 ns | 58.37 ns | 54.60 ns | 6,898.0 ns | 0.5646 | 4.55 KB | +| **EnqueueAndFlush** | **100** | **200** | **1,729.4 ns** | **4.33 ns** | **3.84 ns** | **1,729.1 ns** | **0.2937** | **2.41 KB** | +| EnqueueAndFlush_Parallel | 100 | 200 | 34,233.3 ns | 286.48 ns | 267.97 ns | 34,238.6 ns | 0.9155 | 7.42 KB | +| **EnqueueAndFlush** | **100** | **1000** | **8,515.8 ns** | **21.32 ns** | **18.90 ns** | **8,514.8 ns** | **1.4648** | **12.03 KB** | +| EnqueueAndFlush_Parallel | 100 | 1000 | 317,992.9 ns | 2,076.68 ns | 1,942.53 ns | 318,190.1 ns | 1.9531 | 17.71 KB | diff --git a/src/Sentry/Threading/ScopedCountdownLock.cs b/src/Sentry/Threading/ScopedCountdownLock.cs index 267d9dac02..f3d992f893 100644 --- a/src/Sentry/Threading/ScopedCountdownLock.cs +++ b/src/Sentry/Threading/ScopedCountdownLock.cs @@ -31,13 +31,13 @@ internal ScopedCountdownLock() /// Gets the number of remaining required to exit in order to set/signal the event while a is active. /// When and while a is active, no more can be entered. /// - internal int Count => IsEngaged ? _event.CurrentCount : _event.CurrentCount - 1; + internal int Count => _isEngaged == 1 ? _event.CurrentCount : _event.CurrentCount - 1; /// /// Returns when a is active and the event can be set/signaled by reaching . /// Returns when the can only reach the initial count of when no is active any longer. /// - internal bool IsEngaged => Interlocked.CompareExchange(ref _isEngaged, 1, 1) == 1; + internal bool IsEngaged => _isEngaged == 1; /// /// No will be entered when the has reached , or while the lock is engaged via an active . From f6265b0d763fbf28ee6dd2c44789b66ef1d917f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:44:41 +0200 Subject: [PATCH 09/13] docs: CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a927c5cf49..1dff94ce83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixes - Crontabs now support day names (MON-SUN) and allow step values and ranges to be combined ([#4407](https://github.com/getsentry/sentry-dotnet/pull/4407)) +- Experimental _Structured Logs_: + - `InvalidOperationException` potentially thrown during a race condition in high volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) ### Dependencies From 98b03795d9dba0179bd746ef9174a35645b538cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:47:33 +0200 Subject: [PATCH 10/13] perf(logs): update Benchmark result --- ...gBatchProcessorBenchmarks-report-github.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md index 5559d11f27..8461170b2c 100644 --- a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md +++ b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.StructuredLogBatchProcessorBenchmarks-report-github.md @@ -1,6 +1,6 @@ ``` -BenchmarkDotNet v0.13.12, macOS 15.5 (24F74) [Darwin 24.5.0] +BenchmarkDotNet v0.13.12, macOS 15.6 (24G84) [Darwin 24.6.0] Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores .NET SDK 9.0.301 [Host] : .NET 8.0.14 (8.0.1425.11118), Arm64 RyuJIT AdvSIMD @@ -8,17 +8,17 @@ Apple M3 Pro, 1 CPU, 12 logical and 12 physical cores ``` -| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Median | Gen0 | Allocated | -|------------------------- |----------- |-------------------- |-------------:|------------:|------------:|-------------:|-------:|----------:| -| **EnqueueAndFlush** | **10** | **100** | **1,896.0 ns** | **6.92 ns** | **6.13 ns** | **1,895.0 ns** | **0.6104** | **5 KB** | -| EnqueueAndFlush_Parallel | 10 | 100 | 18,372.7 ns | 362.04 ns | 731.34 ns | 18,432.1 ns | 1.1292 | 9.21 KB | -| **EnqueueAndFlush** | **10** | **200** | **3,683.3 ns** | **11.97 ns** | **10.61 ns** | **3,682.6 ns** | **1.2207** | **10 KB** | -| EnqueueAndFlush_Parallel | 10 | 200 | 41,416.1 ns | 814.20 ns | 1,360.35 ns | 40,730.6 ns | 1.7090 | 14.01 KB | -| **EnqueueAndFlush** | **10** | **1000** | **17,336.4 ns** | **80.76 ns** | **75.54 ns** | **17,324.4 ns** | **6.1035** | **50 KB** | -| EnqueueAndFlush_Parallel | 10 | 1000 | 188,962.0 ns | 1,311.63 ns | 1,226.90 ns | 189,209.6 ns | 4.3945 | 36.6 KB | -| **EnqueueAndFlush** | **100** | **100** | **863.0 ns** | **0.88 ns** | **0.73 ns** | **862.8 ns** | **0.1469** | **1.2 KB** | -| EnqueueAndFlush_Parallel | 100 | 100 | 6,898.0 ns | 58.37 ns | 54.60 ns | 6,898.0 ns | 0.5646 | 4.55 KB | -| **EnqueueAndFlush** | **100** | **200** | **1,729.4 ns** | **4.33 ns** | **3.84 ns** | **1,729.1 ns** | **0.2937** | **2.41 KB** | -| EnqueueAndFlush_Parallel | 100 | 200 | 34,233.3 ns | 286.48 ns | 267.97 ns | 34,238.6 ns | 0.9155 | 7.42 KB | -| **EnqueueAndFlush** | **100** | **1000** | **8,515.8 ns** | **21.32 ns** | **18.90 ns** | **8,514.8 ns** | **1.4648** | **12.03 KB** | -| EnqueueAndFlush_Parallel | 100 | 1000 | 317,992.9 ns | 2,076.68 ns | 1,942.53 ns | 318,190.1 ns | 1.9531 | 17.71 KB | +| Method | BatchCount | OperationsPerInvoke | Mean | Error | StdDev | Gen0 | Allocated | +|------------------------- |----------- |-------------------- |-------------:|------------:|------------:|-------:|----------:| +| **EnqueueAndFlush** | **10** | **100** | **1,793.4 ns** | **13.75 ns** | **12.86 ns** | **0.6104** | **5 KB** | +| EnqueueAndFlush_Parallel | 10 | 100 | 18,550.8 ns | 368.24 ns | 889.34 ns | 1.1292 | 9.16 KB | +| **EnqueueAndFlush** | **10** | **200** | **3,679.8 ns** | **18.65 ns** | **16.53 ns** | **1.2207** | **10 KB** | +| EnqueueAndFlush_Parallel | 10 | 200 | 41,246.4 ns | 508.07 ns | 475.25 ns | 1.7090 | 14.04 KB | +| **EnqueueAndFlush** | **10** | **1000** | **17,239.1 ns** | **62.50 ns** | **58.46 ns** | **6.1035** | **50 KB** | +| EnqueueAndFlush_Parallel | 10 | 1000 | 192,059.3 ns | 956.92 ns | 895.11 ns | 4.3945 | 37.52 KB | +| **EnqueueAndFlush** | **100** | **100** | **866.7 ns** | **1.99 ns** | **1.77 ns** | **0.1469** | **1.2 KB** | +| EnqueueAndFlush_Parallel | 100 | 100 | 6,714.8 ns | 100.75 ns | 94.24 ns | 0.5569 | 4.52 KB | +| **EnqueueAndFlush** | **100** | **200** | **1,714.5 ns** | **3.20 ns** | **3.00 ns** | **0.2937** | **2.41 KB** | +| EnqueueAndFlush_Parallel | 100 | 200 | 43,842.8 ns | 860.74 ns | 1,718.99 ns | 0.9155 | 7.51 KB | +| **EnqueueAndFlush** | **100** | **1000** | **8,537.8 ns** | **9.80 ns** | **9.17 ns** | **1.4648** | **12.03 KB** | +| EnqueueAndFlush_Parallel | 100 | 1000 | 313,421.4 ns | 6,159.27 ns | 6,846.01 ns | 1.9531 | 18.37 KB | From 2608bf401c5c7bd4e15546b8c7528663f2737bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:47:42 +0200 Subject: [PATCH 11/13] merge(logs): CHANGELOG --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3a3a2ba6..a0119f503d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,13 @@ - Experimental _Structured Logs_: - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) + - `InvalidOperationException` potentially thrown during a race condition in high-volume concurrent logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) ## 5.14.1 ### Fixes - Crontabs now support day names (MON-SUN) and allow step values and ranges to be combined ([#4407](https://github.com/getsentry/sentry-dotnet/pull/4407)) -- Experimental _Structured Logs_: - - `InvalidOperationException` potentially thrown during a race condition in high volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) - Ensure the correct Sentry Cocoa SDK framework version is used on iOS ([#4411](https://github.com/getsentry/sentry-dotnet/pull/4411)) ### Dependencies From 21b4f71222c2732c740f4982bd5c75839d67093b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:49:01 +0200 Subject: [PATCH 12/13] docs(logs): rephrase CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0119f503d..9b05282129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Experimental _Structured Logs_: - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - - `InvalidOperationException` potentially thrown during a race condition in high-volume concurrent logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) + - `InvalidOperationException` potentially thrown during a race condition in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) ## 5.14.1 From b1d54410edd21d3643c3b88afc3b427a048bb21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:53:18 +0200 Subject: [PATCH 13/13] docs: rephrase CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b05282129..0b159a4c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Experimental _Structured Logs_: - Remove `IDisposable` from `SentryStructuredLogger`. Disposal is intended through the owning `IHub` instance. ([#4424](https://github.com/getsentry/sentry-dotnet/pull/4424)) - Ensure all buffered logs are sent to Sentry when the application terminates unexpectedly. ([#4425](https://github.com/getsentry/sentry-dotnet/pull/4425)) - - `InvalidOperationException` potentially thrown during a race condition in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) + - `InvalidOperationException` potentially thrown during a race condition, especially in concurrent high-volume logging scenarios ([#4428](https://github.com/getsentry/sentry-dotnet/pull/4428)) ## 5.14.1