// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading; namespace System.Reactive { /// /// Utility methods to handle lock-free combining of Exceptions /// as well as hosting a terminal-exception indicator for /// lock-free termination support. /// internal static class ExceptionHelper { /// /// The singleton instance of the exception indicating a terminal state, /// DO NOT LEAK or signal this via OnError! /// public static Exception Terminated { get; } = new TerminatedException(); /// /// Tries to atomically set the Exception on the given field if it is /// still null. /// /// The target field to try to set atomically. /// The exception to set, not null (not verified). /// True if the operation succeeded, false if the target was not null. public static bool TrySetException(ref Exception field, Exception ex) { return Interlocked.CompareExchange(ref field, ex, null) == null; } /// /// Atomically swaps in the Terminated exception into the field and /// returns the previous exception in that field (which could be the /// Terminated instance too). /// /// The target field to terminate. /// The previous exception in that field before the termination. public static Exception Terminate(ref Exception field) { var current = Volatile.Read(ref field); if (current != Terminated) { current = Interlocked.Exchange(ref field, Terminated); } return current; } /// /// Atomically sets the field to the given new exception or combines /// it with any pre-existing exception as a new AggregateException /// unless the field contains the Terminated instance. /// /// The field to set or combine with. /// The exception to combine with. /// True if successful, false if the field contains the Terminated instance. /// This type of atomic aggregation helps with operators that /// want to delay all errors until all of their sources terminate in some way. public static bool TryAddException(ref Exception field, Exception ex) { for (; ; ) { var current = Volatile.Read(ref field); if (current == Terminated) { return false; } Exception b; if (current == null) { b = ex; } else if (current is AggregateException a) { var list = new List(a.InnerExceptions) { ex }; b = new AggregateException(list); } else { b = new AggregateException(current, ex); } if (Interlocked.CompareExchange(ref field, b, current) == current) { return true; } } } /// /// The class indicating a terminal state as an Exception type. /// private sealed class TerminatedException : Exception { internal TerminatedException() : base("No further exceptions") { } } } }