From 4adfd64adb123a288c889da0db16b0b10b1f3803 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 16 Jun 2025 14:48:07 +0000 Subject: [PATCH 1/2] Add blocking detection suppression for long-running tasks --- .../SamplingTransactionProfilerFactory.cs | 18 ++++++---- src/Sentry/Internal/BackgroundWorker.cs | 6 +++- .../Internal/GarbageCollectionMonitor.cs | 10 ++++-- src/Sentry/Internal/Http/CachingTransport.cs | 7 +++- src/Sentry/Internal/ProcessInfo.cs | 28 ++++++++------- .../Internals/BackgroundWorkerTests.cs | 35 +++++++++++++++++++ 6 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs index 5f0d54453d..a3b2a98a4e 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs @@ -1,5 +1,6 @@ using Sentry.Extensibility; using Sentry.Internal; +using Sentry.Ben.BlockingDetector; namespace Sentry.Profiling; @@ -27,16 +28,19 @@ public SamplingTransactionProfilerFactory(SentryOptions options, TimeSpan startu { _options = options; - _sessionTask = Task.Run(async () => + using (new SuppressBlockingDetection()) { - // This can block up to 30 seconds. The timeout is out of our hands. - var session = SampleProfilerSession.StartNew(options.DiagnosticLogger); + _sessionTask = Task.Run(async () => + { + // This can block up to 30 seconds. The timeout is out of our hands. + var session = SampleProfilerSession.StartNew(options.DiagnosticLogger); - // This can block indefinitely. - await session.WaitForFirstEventAsync().ConfigureAwait(false); + // This can block indefinitely. + await session.WaitForFirstEventAsync().ConfigureAwait(false); - return session; - }); + return session; + }); + } Debug.Assert(TimeSpan.FromSeconds(0) == TimeSpan.Zero); if (startupTimeout != TimeSpan.Zero && !_sessionTask.Wait(startupTimeout)) diff --git a/src/Sentry/Internal/BackgroundWorker.cs b/src/Sentry/Internal/BackgroundWorker.cs index d747922d5a..c469b3370c 100644 --- a/src/Sentry/Internal/BackgroundWorker.cs +++ b/src/Sentry/Internal/BackgroundWorker.cs @@ -2,6 +2,7 @@ using Sentry.Internal.Extensions; using Sentry.Internal.Http; using Sentry.Protocol.Envelopes; +using Sentry.Ben.BlockingDetector; namespace Sentry.Internal; @@ -37,7 +38,10 @@ public BackgroundWorker( _queuedEnvelopeSemaphore = new SemaphoreSlim(0, _maxItems); options.LogDebug("Starting BackgroundWorker."); - WorkerTask = Task.Run(DoWorkAsync); + using (new SuppressBlockingDetection()) + { + WorkerTask = Task.Run(DoWorkAsync); + } } /// diff --git a/src/Sentry/Internal/GarbageCollectionMonitor.cs b/src/Sentry/Internal/GarbageCollectionMonitor.cs index a17486b8fd..48eef84863 100644 --- a/src/Sentry/Internal/GarbageCollectionMonitor.cs +++ b/src/Sentry/Internal/GarbageCollectionMonitor.cs @@ -1,4 +1,5 @@ using Sentry.Extensibility; +using Sentry.Ben.BlockingDetector; namespace Sentry.Internal; @@ -10,8 +11,13 @@ internal sealed class GarbageCollectionMonitor private const int MaxGenerationThreshold = 10; private const int LargeObjectHeapThreshold = 10; - public static Task Start(Action onGarbageCollected, CancellationToken cancellationToken, IGCImplementation? gc = null) => - Task.Run(() => MonitorGarbageCollection(onGarbageCollected, cancellationToken, gc), cancellationToken); + public static Task Start(Action onGarbageCollected, CancellationToken cancellationToken, IGCImplementation? gc = null) + { + using (new SuppressBlockingDetection()) + { + return Task.Run(() => MonitorGarbageCollection(onGarbageCollected, cancellationToken, gc), cancellationToken); + } + } private static void MonitorGarbageCollection(Action onGarbageCollected, CancellationToken cancellationToken, IGCImplementation? gc = null) { diff --git a/src/Sentry/Internal/Http/CachingTransport.cs b/src/Sentry/Internal/Http/CachingTransport.cs index b5ed2cb34a..92cb8c3379 100644 --- a/src/Sentry/Internal/Http/CachingTransport.cs +++ b/src/Sentry/Internal/Http/CachingTransport.cs @@ -1,6 +1,8 @@ +using System.Text.Json; using Sentry.Extensibility; using Sentry.Internal.Extensions; using Sentry.Protocol.Envelopes; +using Sentry.Ben.BlockingDetector; namespace Sentry.Internal.Http; @@ -98,7 +100,10 @@ private void Initialize(bool startWorker) if (startWorker) { _options.LogDebug("Starting CachingTransport worker."); - _worker = Task.Run(CachedTransportBackgroundTaskAsync); + using (new SuppressBlockingDetection()) + { + _worker = Task.Run(CachedTransportBackgroundTaskAsync); + } } else { diff --git a/src/Sentry/Internal/ProcessInfo.cs b/src/Sentry/Internal/ProcessInfo.cs index 414c93e394..09cb21565c 100644 --- a/src/Sentry/Internal/ProcessInfo.cs +++ b/src/Sentry/Internal/ProcessInfo.cs @@ -1,4 +1,5 @@ using Sentry.Extensibility; +using Sentry.Ben.BlockingDetector; namespace Sentry.Internal; @@ -95,20 +96,23 @@ internal ProcessInfo( // This method will give a better precision to the StartupTime at a cost // of calling Process.GetCurrentProcess, on a thread pool thread. var preciseStartupTimeFunc = findPreciseStartupTime ?? GetStartupTime; - PreciseAppStartupTask = Task.Run(() => + using (new SuppressBlockingDetection()) { - try + PreciseAppStartupTask = Task.Run(() => { - StartupTime = preciseStartupTimeFunc(); - } - catch (Exception e) - { - options.LogError(e, "Failure getting precise App startup time."); - //Ignore any exception and stay with the less-precise DateTime.UtcNow value. - } - }).ContinueWith(_ => - // Let the actual task get collected - PreciseAppStartupTask = Task.CompletedTask); + try + { + StartupTime = preciseStartupTimeFunc(); + } + catch (Exception e) + { + options.LogError(e, "Failure getting precise App startup time."); + //Ignore any exception and stay with the less-precise DateTime.UtcNow value. + } + }).ContinueWith(_ => + // Let the actual task get collected + PreciseAppStartupTask = Task.CompletedTask); + } #endif } } diff --git a/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs b/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs index d506a179e6..8ecd2b47da 100644 --- a/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs +++ b/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs @@ -1,5 +1,6 @@ using System.IO.Abstractions.TestingHelpers; using Sentry.Internal.Http; +using Sentry.Ben.BlockingDetector; using BackgroundWorker = Sentry.Internal.BackgroundWorker; namespace Sentry.Tests.Internals; @@ -521,4 +522,38 @@ public async Task FlushAsync_Calls_CachingTransport_FlushAsync() _fixture.Logger.Received(1) .Log(SentryLevel.Debug, "CachingTransport received request to flush the cache."); } + + [Fact] + public void Ctor_SuppressesBlockingDetection_WhenCreatingTask() + { + // Arrange + var listenerState = Substitute.For(); + var monitor = Substitute.For(); + var context = new DetectBlockingSynchronizationContext(monitor); + + // Mock the current sync context to our test context + SynchronizationContext.SetSynchronizationContext(context); + + // Mock the TaskBlockingListener to return our test state + var originalDefaultState = TaskBlockingListener.DefaultState; + TaskBlockingListener.DefaultState = listenerState; + + try + { + // Act + using var sut = _fixture.GetSut(); + + // Assert + // Verify that suppression was called when creating the task + listenerState.Received(1).Suppress(); + // Verify that restoration was called when exiting the using block + listenerState.Received(1).Restore(); + } + finally + { + // Cleanup + TaskBlockingListener.DefaultState = originalDefaultState; + SynchronizationContext.SetSynchronizationContext(null); + } + } } From a6969d367b5506db2acfa514c6977326ff4054ad Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 16 Jun 2025 15:17:59 +0000 Subject: [PATCH 2/2] Format code --- .../SamplingTransactionProfilerFactory.cs | 2 +- src/Sentry/Internal/BackgroundWorker.cs | 2 +- src/Sentry/Internal/GarbageCollectionMonitor.cs | 2 +- src/Sentry/Internal/Http/CachingTransport.cs | 2 +- src/Sentry/Internal/ProcessInfo.cs | 2 +- test/Sentry.Tests/Internals/BackgroundWorkerTests.cs | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs index a3b2a98a4e..386fd1f4f2 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs @@ -1,6 +1,6 @@ +using Sentry.Ben.BlockingDetector; using Sentry.Extensibility; using Sentry.Internal; -using Sentry.Ben.BlockingDetector; namespace Sentry.Profiling; diff --git a/src/Sentry/Internal/BackgroundWorker.cs b/src/Sentry/Internal/BackgroundWorker.cs index c469b3370c..f7a283f3ed 100644 --- a/src/Sentry/Internal/BackgroundWorker.cs +++ b/src/Sentry/Internal/BackgroundWorker.cs @@ -1,8 +1,8 @@ +using Sentry.Ben.BlockingDetector; using Sentry.Extensibility; using Sentry.Internal.Extensions; using Sentry.Internal.Http; using Sentry.Protocol.Envelopes; -using Sentry.Ben.BlockingDetector; namespace Sentry.Internal; diff --git a/src/Sentry/Internal/GarbageCollectionMonitor.cs b/src/Sentry/Internal/GarbageCollectionMonitor.cs index 48eef84863..825f628172 100644 --- a/src/Sentry/Internal/GarbageCollectionMonitor.cs +++ b/src/Sentry/Internal/GarbageCollectionMonitor.cs @@ -1,5 +1,5 @@ -using Sentry.Extensibility; using Sentry.Ben.BlockingDetector; +using Sentry.Extensibility; namespace Sentry.Internal; diff --git a/src/Sentry/Internal/Http/CachingTransport.cs b/src/Sentry/Internal/Http/CachingTransport.cs index 92cb8c3379..039226d7d0 100644 --- a/src/Sentry/Internal/Http/CachingTransport.cs +++ b/src/Sentry/Internal/Http/CachingTransport.cs @@ -1,8 +1,8 @@ using System.Text.Json; +using Sentry.Ben.BlockingDetector; using Sentry.Extensibility; using Sentry.Internal.Extensions; using Sentry.Protocol.Envelopes; -using Sentry.Ben.BlockingDetector; namespace Sentry.Internal.Http; diff --git a/src/Sentry/Internal/ProcessInfo.cs b/src/Sentry/Internal/ProcessInfo.cs index 09cb21565c..65d3bc13b8 100644 --- a/src/Sentry/Internal/ProcessInfo.cs +++ b/src/Sentry/Internal/ProcessInfo.cs @@ -1,5 +1,5 @@ -using Sentry.Extensibility; using Sentry.Ben.BlockingDetector; +using Sentry.Extensibility; namespace Sentry.Internal; diff --git a/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs b/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs index 8ecd2b47da..b52b468a76 100644 --- a/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs +++ b/test/Sentry.Tests/Internals/BackgroundWorkerTests.cs @@ -1,6 +1,6 @@ using System.IO.Abstractions.TestingHelpers; -using Sentry.Internal.Http; using Sentry.Ben.BlockingDetector; +using Sentry.Internal.Http; using BackgroundWorker = Sentry.Internal.BackgroundWorker; namespace Sentry.Tests.Internals; @@ -530,19 +530,19 @@ public void Ctor_SuppressesBlockingDetection_WhenCreatingTask() var listenerState = Substitute.For(); var monitor = Substitute.For(); var context = new DetectBlockingSynchronizationContext(monitor); - + // Mock the current sync context to our test context SynchronizationContext.SetSynchronizationContext(context); - + // Mock the TaskBlockingListener to return our test state var originalDefaultState = TaskBlockingListener.DefaultState; TaskBlockingListener.DefaultState = listenerState; - + try { // Act using var sut = _fixture.GetSut(); - + // Assert // Verify that suppression was called when creating the task listenerState.Received(1).Suppress();