diff --git a/src/Polly.Core/Hedging/Controller/TaskExecution.cs b/src/Polly.Core/Hedging/Controller/TaskExecution.cs index fee82e68081..a87798351db 100644 --- a/src/Polly.Core/Hedging/Controller/TaskExecution.cs +++ b/src/Polly.Core/Hedging/Controller/TaskExecution.cs @@ -199,6 +199,7 @@ public async ValueTask ResetAsync() _stopExecutionTimestamp = 0; } + [DebuggerDisableUserUnhandledExceptions] private async Task ExecuteSecondaryActionAsync(Func>> action) { Outcome outcome; @@ -218,6 +219,7 @@ private async Task ExecuteSecondaryActionAsync(Func>> actio private async Task ExecuteCreateActionException(Exception e) => await UpdateOutcomeAsync(Polly.Outcome.FromException(e)).ConfigureAwait(Context.ContinueOnCapturedContext); + [DebuggerDisableUserUnhandledExceptions] private async Task ExecutePrimaryActionAsync(Func>> primaryCallback, TState state) { Outcome outcome; diff --git a/src/Polly.Core/Outcome.TResult.cs b/src/Polly.Core/Outcome.TResult.cs index ad5a077fce9..672451ab651 100644 --- a/src/Polly.Core/Outcome.TResult.cs +++ b/src/Polly.Core/Outcome.TResult.cs @@ -90,5 +90,4 @@ internal TResult GetResultOrRethrow() ExceptionDispatchInfo?.Throw(); return Result!; } - } diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index 87d5cf5f18d..22c5a9d396e 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -36,4 +36,8 @@ + + + + diff --git a/src/Polly.Core/ResilienceContext.cs b/src/Polly.Core/ResilienceContext.cs index d77d1d2a1cc..d20d3b03866 100644 --- a/src/Polly.Core/ResilienceContext.cs +++ b/src/Polly.Core/ResilienceContext.cs @@ -74,9 +74,11 @@ internal void InitializeFrom(ResilienceContext context, CancellationToken cancel Properties.AddOrReplaceProperties(context.Properties); } +#pragma warning disable S3236 // Remove this argument from the method call; it hides the caller information. [ExcludeFromCodeCoverage] [Conditional("DEBUG")] internal void AssertInitialized() => Debug.Assert(IsInitialized, "The resilience context is not initialized."); +#pragma warning restore S3236 // Remove this argument from the method call; it hides the caller information. internal ResilienceContext Initialize(bool isSynchronous) { diff --git a/src/Polly.Core/ResiliencePipeline.Async.cs b/src/Polly.Core/ResiliencePipeline.Async.cs index b556446d4d7..2505baebf49 100644 --- a/src/Polly.Core/ResiliencePipeline.Async.cs +++ b/src/Polly.Core/ResiliencePipeline.Async.cs @@ -25,7 +25,7 @@ public async ValueTask ExecuteAsync( InitializeAsyncContext(context); var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { @@ -60,7 +60,7 @@ public async ValueTask ExecuteAsync( InitializeAsyncContext(context); var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { @@ -99,7 +99,7 @@ public async ValueTask ExecuteAsync( try { var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { @@ -140,7 +140,7 @@ public async ValueTask ExecuteAsync( try { var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { diff --git a/src/Polly.Core/ResiliencePipeline.AsyncT.cs b/src/Polly.Core/ResiliencePipeline.AsyncT.cs index f12a7521b6c..72e9d85ce35 100644 --- a/src/Polly.Core/ResiliencePipeline.AsyncT.cs +++ b/src/Polly.Core/ResiliencePipeline.AsyncT.cs @@ -53,7 +53,7 @@ public async ValueTask ExecuteAsync( InitializeAsyncContext(context); var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { @@ -88,7 +88,7 @@ public async ValueTask ExecuteAsync( InitializeAsyncContext(context); var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { @@ -127,7 +127,7 @@ public async ValueTask ExecuteAsync( try { var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { @@ -168,7 +168,7 @@ public async ValueTask ExecuteAsync( try { var outcome = await Component.ExecuteCore( - static async (context, state) => + [DebuggerDisableUserUnhandledExceptions] static async (context, state) => { try { diff --git a/src/Polly.Core/ResiliencePipeline.Sync.cs b/src/Polly.Core/ResiliencePipeline.Sync.cs index 32cb19ad501..724276270e9 100644 --- a/src/Polly.Core/ResiliencePipeline.Sync.cs +++ b/src/Polly.Core/ResiliencePipeline.Sync.cs @@ -24,7 +24,7 @@ public void Execute( InitializeSyncContext(context); Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -56,7 +56,7 @@ public void Execute( InitializeSyncContext(context); Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -92,7 +92,7 @@ public void Execute( try { Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -130,7 +130,7 @@ public void Execute( try { Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -169,7 +169,7 @@ public void Execute( try { Component.ExecuteCoreSync( - static (_, state) => + [DebuggerDisableUserUnhandledExceptions] static (_, state) => { try { @@ -204,7 +204,7 @@ public void Execute(Action callback) try { Component.ExecuteCoreSync( - static (_, state) => + [DebuggerDisableUserUnhandledExceptions] static (_, state) => { try { diff --git a/src/Polly.Core/ResiliencePipeline.SyncT.cs b/src/Polly.Core/ResiliencePipeline.SyncT.cs index 844f42d802e..da748e56d75 100644 --- a/src/Polly.Core/ResiliencePipeline.SyncT.cs +++ b/src/Polly.Core/ResiliencePipeline.SyncT.cs @@ -26,7 +26,7 @@ public TResult Execute( InitializeSyncContext(context); return Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -60,7 +60,7 @@ public TResult Execute( InitializeSyncContext(context); return Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -95,7 +95,7 @@ public TResult Execute( try { return Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { @@ -131,7 +131,7 @@ public TResult Execute(Func callback) try { return Component.ExecuteCoreSync( - static (_, state) => + [DebuggerDisableUserUnhandledExceptions] static (_, state) => { try { @@ -169,7 +169,7 @@ public TResult Execute(Func callback, TState s try { return Component.ExecuteCoreSync( - static (_, state) => + [DebuggerDisableUserUnhandledExceptions] static (_, state) => { try { @@ -211,7 +211,7 @@ public TResult Execute( try { return Component.ExecuteCoreSync( - static (context, state) => + [DebuggerDisableUserUnhandledExceptions] static (context, state) => { try { diff --git a/src/Polly.Core/Retry/RetryHelper.cs b/src/Polly.Core/Retry/RetryHelper.cs index 1acbbf8f6cc..9736a83375c 100644 --- a/src/Polly.Core/Retry/RetryHelper.cs +++ b/src/Polly.Core/Retry/RetryHelper.cs @@ -124,7 +124,9 @@ private static TimeSpan DecorrelatedJitterBackoffV2(int attempt, TimeSpan baseDe long ticks = (long)Math.Min(formulaIntrinsicValue * RpScalingFactor * targetTicksFirstDelay, MaxTimeSpanTicks); +#pragma warning disable S3236 // Remove this argument from the method call; it hides the caller information. Debug.Assert(ticks >= 0, "ticks cannot be negative"); +#pragma warning restore S3236 // Remove this argument from the method call; it hides the caller information. return TimeSpan.FromTicks(ticks); } diff --git a/src/Polly.Core/Retry/RetryResilienceStrategy.cs b/src/Polly.Core/Retry/RetryResilienceStrategy.cs index 6402875d69b..3b378b3a5a3 100644 --- a/src/Polly.Core/Retry/RetryResilienceStrategy.cs +++ b/src/Polly.Core/Retry/RetryResilienceStrategy.cs @@ -83,7 +83,9 @@ protected internal override async ValueTask> ExecuteCore(Func } } +#pragma warning disable S3236 // Remove this argument from the method call; it hides the caller information. Debug.Assert(delay >= TimeSpan.Zero, "The delay cannot be negative."); +#pragma warning restore S3236 // Remove this argument from the method call; it hides the caller information. var onRetryArgs = new OnRetryArguments(context, outcome, attempt, delay, executionTime); _telemetry.Report, T>(new(ResilienceEventSeverity.Warning, RetryConstants.OnRetryEvent), onRetryArgs); diff --git a/src/Polly.Core/Utils/StrategyHelper.cs b/src/Polly.Core/Utils/StrategyHelper.cs index e0f1d9568a0..2b63321b079 100644 --- a/src/Polly.Core/Utils/StrategyHelper.cs +++ b/src/Polly.Core/Utils/StrategyHelper.cs @@ -4,6 +4,7 @@ internal static class StrategyHelper { + [DebuggerDisableUserUnhandledExceptions] public static ValueTask> ExecuteCallbackSafeAsync( Func>> callback, ResilienceContext context, @@ -29,6 +30,7 @@ public static ValueTask> ExecuteCallbackSafeAsync>(Outcome.FromException(e)); } + [DebuggerDisableUserUnhandledExceptions] static async ValueTask> AwaitTask(ValueTask> task, bool continueOnCapturedContext) { try diff --git a/src/Polly.Core/Utils/TaskHelper.cs b/src/Polly.Core/Utils/TaskHelper.cs index cd998dea2b7..d874af21aba 100644 --- a/src/Polly.Core/Utils/TaskHelper.cs +++ b/src/Polly.Core/Utils/TaskHelper.cs @@ -1,5 +1,6 @@ namespace Polly.Utils; +#pragma warning disable S3236 // Remove this argument from the method call; it hides the caller information. #pragma warning disable S5034 // "ValueTask" should be consumed correctly internal static class TaskHelper diff --git a/src/Polly/Caching/AsyncCacheEngine.cs b/src/Polly/Caching/AsyncCacheEngine.cs index ccf9c2b195a..fedb55fc71d 100644 --- a/src/Polly/Caching/AsyncCacheEngine.cs +++ b/src/Polly/Caching/AsyncCacheEngine.cs @@ -3,6 +3,7 @@ namespace Polly.Caching; internal static class AsyncCacheEngine { + [DebuggerDisableUserUnhandledExceptions] internal static async Task ImplementationAsync( IAsyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, diff --git a/src/Polly/Caching/CacheEngine.cs b/src/Polly/Caching/CacheEngine.cs index 796b3e00ab4..c89fae9c28e 100644 --- a/src/Polly/Caching/CacheEngine.cs +++ b/src/Polly/Caching/CacheEngine.cs @@ -3,6 +3,7 @@ namespace Polly.Caching; internal static class CacheEngine { + [DebuggerDisableUserUnhandledExceptions] internal static TResult Implementation( ISyncCacheProvider cacheProvider, ITtlStrategy ttlStrategy, diff --git a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs index 1a5905fa228..bb3ec74af91 100644 --- a/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/AsyncCircuitBreakerEngine.cs @@ -2,6 +2,7 @@ internal static class AsyncCircuitBreakerEngine { + [DebuggerDisableUserUnhandledExceptions] internal static async Task ImplementationAsync( Func> action, Context context, diff --git a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs index ef8883aac28..c5037a4fb01 100644 --- a/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs +++ b/src/Polly/CircuitBreaker/CircuitBreakerEngine.cs @@ -4,6 +4,7 @@ namespace Polly.CircuitBreaker; internal static class CircuitBreakerEngine { + [DebuggerDisableUserUnhandledExceptions] internal static TResult Implementation( Func action, Context context, diff --git a/src/Polly/Fallback/AsyncFallbackEngine.cs b/src/Polly/Fallback/AsyncFallbackEngine.cs index 79a58554b32..f2c268fe808 100644 --- a/src/Polly/Fallback/AsyncFallbackEngine.cs +++ b/src/Polly/Fallback/AsyncFallbackEngine.cs @@ -3,6 +3,7 @@ namespace Polly.Fallback; internal static class AsyncFallbackEngine { + [DebuggerDisableUserUnhandledExceptions] internal static async Task ImplementationAsync( Func> action, Context context, diff --git a/src/Polly/Fallback/FallbackEngine.cs b/src/Polly/Fallback/FallbackEngine.cs index 1193286891f..96351071a9d 100644 --- a/src/Polly/Fallback/FallbackEngine.cs +++ b/src/Polly/Fallback/FallbackEngine.cs @@ -3,6 +3,7 @@ namespace Polly.Fallback; internal static class FallbackEngine { + [DebuggerDisableUserUnhandledExceptions] internal static TResult Implementation( Func action, Context context, diff --git a/src/Polly/Polly.csproj b/src/Polly/Polly.csproj index 3a15d8cba4d..5f1742bd8b2 100644 --- a/src/Polly/Polly.csproj +++ b/src/Polly/Polly.csproj @@ -26,4 +26,8 @@ + + + + diff --git a/src/Polly/Retry/AsyncRetryEngine.cs b/src/Polly/Retry/AsyncRetryEngine.cs index 7237366b8f2..6672249b53d 100644 --- a/src/Polly/Retry/AsyncRetryEngine.cs +++ b/src/Polly/Retry/AsyncRetryEngine.cs @@ -3,6 +3,7 @@ namespace Polly.Retry; internal static class AsyncRetryEngine { + [DebuggerDisableUserUnhandledExceptions] internal static async Task ImplementationAsync( Func> action, Context context, diff --git a/src/Polly/Retry/RetryEngine.cs b/src/Polly/Retry/RetryEngine.cs index 966da9858d8..743e08711bf 100644 --- a/src/Polly/Retry/RetryEngine.cs +++ b/src/Polly/Retry/RetryEngine.cs @@ -3,6 +3,7 @@ namespace Polly.Retry; internal static class RetryEngine { + [DebuggerDisableUserUnhandledExceptions] internal static TResult Implementation( Func action, Context context, diff --git a/src/Polly/Timeout/AsyncTimeoutEngine.cs b/src/Polly/Timeout/AsyncTimeoutEngine.cs index fc008c3b4fc..dfa168fd4b1 100644 --- a/src/Polly/Timeout/AsyncTimeoutEngine.cs +++ b/src/Polly/Timeout/AsyncTimeoutEngine.cs @@ -2,6 +2,7 @@ internal static class AsyncTimeoutEngine { + [DebuggerDisableUserUnhandledExceptions] internal static async Task ImplementationAsync( Func> action, Context context, @@ -57,7 +58,11 @@ internal static async Task ImplementationAsync( // See https://github.com/App-vNext/Polly/issues/722. if (!combinedTokenSource.IsCancellationRequested && timeoutCancellationTokenSource.IsCancellationRequested) { +#if NET8_0_OR_GREATER + await combinedTokenSource.CancelAsync().ConfigureAwait(false); +#else combinedTokenSource.Cancel(); +#endif } } } diff --git a/src/Polly/Timeout/TimeoutEngine.cs b/src/Polly/Timeout/TimeoutEngine.cs index 2afbfd0685c..8b470f67dbd 100644 --- a/src/Polly/Timeout/TimeoutEngine.cs +++ b/src/Polly/Timeout/TimeoutEngine.cs @@ -4,6 +4,7 @@ namespace Polly.Timeout; internal static class TimeoutEngine { + [DebuggerDisableUserUnhandledExceptions] internal static TResult Implementation( Func action, Context context, diff --git a/src/Shared/DebuggerDisableUserUnhandledExceptionsAttribute.cs b/src/Shared/DebuggerDisableUserUnhandledExceptionsAttribute.cs new file mode 100644 index 00000000000..f14cbae0cb0 --- /dev/null +++ b/src/Shared/DebuggerDisableUserUnhandledExceptionsAttribute.cs @@ -0,0 +1,21 @@ +#pragma warning disable +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Adapted from https://github.com/dotnet/runtime/blob/bffe34e0c7ff8a05e79d884ed8447426aae17bfb/src/libraries/System.Private.CoreLib/src/System/Diagnostics/DebuggerDisableUserUnhandledExceptionsAttribute.cs +// See the following links for more information and context: +// https://github.com/dotnet/runtime/issues/103105, +// https://github.com/dotnet/runtime/pull/104813 +// https://github.com/dotnet/aspnetcore/issues/57085 + +namespace System.Diagnostics; + +/// +/// If a .NET Debugger is attached which supports the Debugger.BreakForUserUnhandledException(Exception) API, +/// this attribute will prevent the debugger from breaking on user-unhandled exceptions when the +/// exception is caught by a method with this attribute, unless BreakForUserUnhandledException is called. +/// +[AttributeUsage(AttributeTargets.Method)] +internal sealed class DebuggerDisableUserUnhandledExceptionsAttribute : Attribute +{ +}