ExceptionHelper.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections.Generic;
  5. using System.Threading;
  6. namespace System.Reactive
  7. {
  8. /// <summary>
  9. /// Utility methods to handle lock-free combining of Exceptions
  10. /// as well as hosting a terminal-exception indicator for
  11. /// lock-free termination support.
  12. /// </summary>
  13. internal static class ExceptionHelper
  14. {
  15. /// <summary>
  16. /// The singleton instance of the exception indicating a terminal state,
  17. /// DO NOT LEAK or signal this via OnError!
  18. /// </summary>
  19. public static Exception Terminated { get; } = new TerminatedException();
  20. /// <summary>
  21. /// Tries to atomically set the Exception on the given field if it is
  22. /// still null.
  23. /// </summary>
  24. /// <param name="field">The target field to try to set atomically.</param>
  25. /// <param name="ex">The exception to set, not null (not verified).</param>
  26. /// <returns>True if the operation succeeded, false if the target was not null.</returns>
  27. public static bool TrySetException(ref Exception field, Exception ex)
  28. {
  29. return Interlocked.CompareExchange(ref field, ex, null) == null;
  30. }
  31. /// <summary>
  32. /// Atomically swaps in the Terminated exception into the field and
  33. /// returns the previous exception in that field (which could be the
  34. /// Terminated instance too).
  35. /// </summary>
  36. /// <param name="field">The target field to terminate.</param>
  37. /// <returns>The previous exception in that field before the termination.</returns>
  38. public static Exception Terminate(ref Exception field)
  39. {
  40. var current = Volatile.Read(ref field);
  41. if (current != Terminated)
  42. {
  43. current = Interlocked.Exchange(ref field, Terminated);
  44. }
  45. return current;
  46. }
  47. /// <summary>
  48. /// Atomically sets the field to the given new exception or combines
  49. /// it with any pre-existing exception as a new AggregateException
  50. /// unless the field contains the Terminated instance.
  51. /// </summary>
  52. /// <param name="field">The field to set or combine with.</param>
  53. /// <param name="ex">The exception to combine with.</param>
  54. /// <returns>True if successful, false if the field contains the Terminated instance.</returns>
  55. /// <remarks>This type of atomic aggregation helps with operators that
  56. /// want to delay all errors until all of their sources terminate in some way.</remarks>
  57. public static bool TryAddException(ref Exception field, Exception ex)
  58. {
  59. for (; ; )
  60. {
  61. var current = Volatile.Read(ref field);
  62. if (current == Terminated)
  63. {
  64. return false;
  65. }
  66. Exception b;
  67. if (current == null)
  68. {
  69. b = ex;
  70. }
  71. else if (current is AggregateException a)
  72. {
  73. var list = new List<Exception>(a.InnerExceptions)
  74. {
  75. ex
  76. };
  77. b = new AggregateException(list);
  78. }
  79. else
  80. {
  81. b = new AggregateException(current, ex);
  82. }
  83. if (Interlocked.CompareExchange(ref field, b, current) == current)
  84. {
  85. return true;
  86. }
  87. }
  88. }
  89. /// <summary>
  90. /// The class indicating a terminal state as an Exception type.
  91. /// </summary>
  92. private sealed class TerminatedException : Exception
  93. {
  94. internal TerminatedException() : base("No further exceptions")
  95. {
  96. }
  97. }
  98. }
  99. }