diff --git a/Hudl.Mjolnir/Command/Command.cs b/Hudl.Mjolnir/Command/Command.cs index 2724100..2ad4cfd 100644 --- a/Hudl.Mjolnir/Command/Command.cs +++ b/Hudl.Mjolnir/Command/Command.cs @@ -380,9 +380,17 @@ private async Task ExecuteWithBreaker(CancellationToken cancellationTok CircuitBreaker.MarkSuccess(stopwatch.ElapsedMilliseconds); CircuitBreaker.Metrics.MarkCommandSuccess(); } - catch (Exception) + catch (Exception e) { - CircuitBreaker.Metrics.MarkCommandFailure(); + if (CommandContext.IsExceptionIgnored(e.GetType())) + { + CircuitBreaker.Metrics.MarkCommandSuccess(); + } + else + { + CircuitBreaker.Metrics.MarkCommandFailure(); + } + throw; } diff --git a/Hudl.Mjolnir/Command/CommandContext.cs b/Hudl.Mjolnir/Command/CommandContext.cs index 0377738..083ba06 100644 --- a/Hudl.Mjolnir/Command/CommandContext.cs +++ b/Hudl.Mjolnir/Command/CommandContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using Hudl.Config; using Hudl.Mjolnir.Breaker; using Hudl.Mjolnir.External; @@ -59,6 +60,10 @@ public sealed class CommandContext private readonly ConcurrentDictionary> _pools = new ConcurrentDictionary>(); private readonly ConcurrentDictionary> _fallbackSemaphores = new ConcurrentDictionary>(); + // This is a Dictionary only because there's no great concurrent Set type available. Just + // use the keys if checking for a type. + private readonly ConcurrentDictionary _ignoredExceptionTypes = new ConcurrentDictionary(); + private IStats _stats = new IgnoringStats(); private CommandContext() {} @@ -84,6 +89,32 @@ public static IStats Stats } } + /// + /// Ignored exception types won't count toward breakers tripping or other error counters. + /// Useful for things like validation, where the system isn't having any problems and the + /// caller needs to validate before invoking. This list is most applicable when using + /// [Command] attributes, since extending Command offers the ability to catch these types + /// specifically within Execute() - though there may still be some benefit in extended + /// Commands for validation-like situations where throwing is still desired. + /// + public static void IgnoreExceptions(HashSet types) + { + if (types == null || types.Count == 0) + { + return; + } + + foreach (var type in types) + { + Instance._ignoredExceptionTypes.TryAdd(type, true); + } + } + + internal static bool IsExceptionIgnored(Type type) + { + return Instance._ignoredExceptionTypes.ContainsKey(type); + } + internal static ICircuitBreaker GetCircuitBreaker(GroupKey key) { if (key == null) diff --git a/Hudl.Mjolnir/Properties/AssemblyInfo.cs b/Hudl.Mjolnir/Properties/AssemblyInfo.cs index 9829a92..5768b56 100644 --- a/Hudl.Mjolnir/Properties/AssemblyInfo.cs +++ b/Hudl.Mjolnir/Properties/AssemblyInfo.cs @@ -27,10 +27,10 @@ [assembly: Guid("97b23684-6c4a-4749-b307-5867cbce2dff")] // Used for NuGet packaging, uses semantic versioning: major.minor.patch-prerelease. -[assembly: AssemblyInformationalVersion("2.2.0")] +[assembly: AssemblyInformationalVersion("2.3.0")] // Keep this the same as AssemblyInformationalVersion. -[assembly: AssemblyFileVersion("2.2.0")] +[assembly: AssemblyFileVersion("2.3.0")] // ONLY change this when the major version changes; never with minor/patch/build versions. // It'll almost always be the major version followed by three zeroes (e.g. 1.0.0.0).