Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / mscorlib / system / threading / Tasks / TaskExceptionHolder.cs
blobc078b63360147365296d23e32f7cfcbb7e87b033
1 // ==++==
2 //
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 //
5 // ==--==
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
7 //
8 // TaskExceptionHolder.cs
9 //
10 // <OWNER>Microsoft</OWNER>
12 // An abstraction for holding and aggregating exceptions.
14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
16 // Disable the "reference to volatile field not treated as volatile" error.
17 #pragma warning disable 0420
19 namespace System.Threading.Tasks
21 using System;
22 using System.Collections.Generic;
23 using System.Collections.ObjectModel;
24 using System.Diagnostics.Contracts;
25 using System.Runtime.ExceptionServices;
26 using System.Security;
28 /// <summary>
29 /// An exception holder manages a list of exceptions for one particular task.
30 /// It offers the ability to aggregate, but more importantly, also offers intrinsic
31 /// support for propagating unhandled exceptions that are never observed. It does
32 /// this by aggregating and throwing if the holder is ever GC'd without the holder's
33 /// contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc).
34 /// This behavior is prominent in .NET 4 but is suppressed by default beyond that release.
35 /// </summary>
36 internal class TaskExceptionHolder
38 /// <summary>Whether we should propagate exceptions on the finalizer.</summary>
39 private readonly static bool s_failFastOnUnobservedException = ShouldFailFastOnUnobservedException();
40 /// <summary>Whether the AppDomain has started to unload.</summary>
41 private static volatile bool s_domainUnloadStarted;
42 /// <summary>An event handler used to notify of domain unload.</summary>
43 private static volatile EventHandler s_adUnloadEventHandler;
45 /// <summary>The task with which this holder is associated.</summary>
46 private readonly Task m_task;
47 /// <summary>
48 /// The lazily-initialized list of faulting exceptions. Volatile
49 /// so that it may be read to determine whether any exceptions were stored.
50 /// </summary>
51 private volatile List<ExceptionDispatchInfo> m_faultExceptions;
52 /// <summary>An exception that triggered the task to cancel.</summary>
53 private ExceptionDispatchInfo m_cancellationException;
54 /// <summary>Whether the holder was "observed" and thus doesn't cause finalization behavior.</summary>
55 private volatile bool m_isHandled;
57 /// <summary>
58 /// Creates a new holder; it will be registered for finalization.
59 /// </summary>
60 /// <param name="task">The task this holder belongs to.</param>
61 internal TaskExceptionHolder(Task task)
63 Contract.Requires(task != null, "Expected a non-null task.");
64 m_task = task;
65 EnsureADUnloadCallbackRegistered();
68 [SecuritySafeCritical]
69 private static bool ShouldFailFastOnUnobservedException()
71 bool shouldFailFast = false;
72 #if !FEATURE_CORECLR
73 shouldFailFast = System.CLRConfig.CheckThrowUnobservedTaskExceptions();
74 #endif
75 return shouldFailFast;
78 private static void EnsureADUnloadCallbackRegistered()
80 #if MONO_FEATURE_MULTIPLE_APPDOMAINS
81 if (s_adUnloadEventHandler == null &&
82 Interlocked.CompareExchange( ref s_adUnloadEventHandler,
83 AppDomainUnloadCallback,
84 null) == null)
86 AppDomain.CurrentDomain.DomainUnload += s_adUnloadEventHandler;
88 #endif
91 private static void AppDomainUnloadCallback(object sender, EventArgs e)
93 s_domainUnloadStarted = true;
96 /// <summary>
97 /// A finalizer that repropagates unhandled exceptions.
98 /// </summary>
99 ~TaskExceptionHolder()
101 // Raise unhandled exceptions only when we know that neither the process or nor the appdomain is being torn down.
102 // We need to do this filtering because all TaskExceptionHolders will be finalized during shutdown or unload
103 // regardles of reachability of the task (i.e. even if the user code was about to observe the task's exception),
104 // which can otherwise lead to spurious crashes during shutdown.
105 if (m_faultExceptions != null && !m_isHandled &&
106 !Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload() && !s_domainUnloadStarted)
108 // We don't want to crash the finalizer thread if any ThreadAbortExceptions
109 // occur in the list or in any nested AggregateExceptions.
110 // (Don't rethrow ThreadAbortExceptions.)
111 foreach (ExceptionDispatchInfo edi in m_faultExceptions)
113 var exp = edi.SourceException;
114 AggregateException aggExp = exp as AggregateException;
115 if (aggExp != null)
117 AggregateException flattenedAggExp = aggExp.Flatten();
118 foreach (Exception innerExp in flattenedAggExp.InnerExceptions)
120 if (innerExp is ThreadAbortException)
121 return;
124 else if (exp is ThreadAbortException)
126 return;
130 // We will only propagate if this is truly unhandled. The reason this could
131 // ever occur is somewhat subtle: if a Task's exceptions are observed in some
132 // other finalizer, and the Task was finalized before the holder, the holder
133 // will have been marked as handled before even getting here.
135 // Give users a chance to keep this exception from crashing the process
137 // First, publish the unobserved exception and allow users to observe it
138 AggregateException exceptionToThrow = new AggregateException(
139 Environment.GetResourceString("TaskExceptionHolder_UnhandledException"),
140 m_faultExceptions);
141 UnobservedTaskExceptionEventArgs ueea = new UnobservedTaskExceptionEventArgs(exceptionToThrow);
142 TaskScheduler.PublishUnobservedTaskException(m_task, ueea);
144 // Now, if we are still unobserved and we're configured to crash on unobserved, throw the exception.
145 // We need to publish the event above even if we're not going to crash, hence
146 // why this check doesn't come at the beginning of the method.
147 if (s_failFastOnUnobservedException && !ueea.m_observed)
149 throw exceptionToThrow;
154 /// <summary>Gets whether the exception holder is currently storing any exceptions for faults.</summary>
155 internal bool ContainsFaultList { get { return m_faultExceptions != null; } }
157 /// <summary>
158 /// Add an exception to the holder. This will ensure the holder is
159 /// in the proper state (handled/unhandled) depending on the list's contents.
160 /// </summary>
161 /// <param name="exceptionObject">
162 /// An exception object (either an Exception, an ExceptionDispatchInfo,
163 /// an IEnumerable{Exception}, or an IEnumerable{ExceptionDispatchInfo})
164 /// to add to the list.
165 /// </param>
166 /// <remarks>
167 /// Must be called under lock.
168 /// </remarks>
169 internal void Add(object exceptionObject)
171 Add(exceptionObject, representsCancellation: false);
174 /// <summary>
175 /// Add an exception to the holder. This will ensure the holder is
176 /// in the proper state (handled/unhandled) depending on the list's contents.
177 /// </summary>
178 /// <param name="representsCancellation">
179 /// Whether the exception represents a cancellation request (true) or a fault (false).
180 /// </param>
181 /// <param name="exceptionObject">
182 /// An exception object (either an Exception, an ExceptionDispatchInfo,
183 /// an IEnumerable{Exception}, or an IEnumerable{ExceptionDispatchInfo})
184 /// to add to the list.
185 /// </param>
186 /// <remarks>
187 /// Must be called under lock.
188 /// </remarks>
189 internal void Add(object exceptionObject, bool representsCancellation)
191 Contract.Requires(exceptionObject != null, "TaskExceptionHolder.Add(): Expected a non-null exceptionObject");
192 Contract.Requires(
193 exceptionObject is Exception || exceptionObject is IEnumerable<Exception> ||
194 exceptionObject is ExceptionDispatchInfo || exceptionObject is IEnumerable<ExceptionDispatchInfo>,
195 "TaskExceptionHolder.Add(): Expected Exception, IEnumerable<Exception>, ExceptionDispatchInfo, or IEnumerable<ExceptionDispatchInfo>");
197 if (representsCancellation) SetCancellationException(exceptionObject);
198 else AddFaultException(exceptionObject);
201 /// <summary>Sets the cancellation exception.</summary>
202 /// <param name="exceptionObject">The cancellation exception.</param>
203 /// <remarks>
204 /// Must be called under lock.
205 /// </remarks>
206 private void SetCancellationException(object exceptionObject)
208 Contract.Requires(exceptionObject != null, "Expected exceptionObject to be non-null.");
210 Contract.Assert(m_cancellationException == null,
211 "Expected SetCancellationException to be called only once.");
212 // Breaking this assumption will overwrite a previously OCE,
213 // and implies something may be wrong elsewhere, since there should only ever be one.
215 Contract.Assert(m_faultExceptions == null,
216 "Expected SetCancellationException to be called before any faults were added.");
217 // Breaking this assumption shouldn't hurt anything here, but it implies something may be wrong elsewhere.
218 // If this changes, make sure to only conditionally mark as handled below.
220 // Store the cancellation exception
221 var oce = exceptionObject as OperationCanceledException;
222 if (oce != null)
224 m_cancellationException = ExceptionDispatchInfo.Capture(oce);
226 else
228 var edi = exceptionObject as ExceptionDispatchInfo;
229 Contract.Assert(edi != null && edi.SourceException is OperationCanceledException,
230 "Expected an OCE or an EDI that contained an OCE");
231 m_cancellationException = edi;
234 // This is just cancellation, and there are no faults, so mark the holder as handled.
235 MarkAsHandled(false);
238 /// <summary>Adds the exception to the fault list.</summary>
239 /// <param name="exceptionObject">The exception to store.</param>
240 /// <remarks>
241 /// Must be called under lock.
242 /// </remarks>
243 private void AddFaultException(object exceptionObject)
245 Contract.Requires(exceptionObject != null, "AddFaultException(): Expected a non-null exceptionObject");
247 // Initialize the exceptions list if necessary. The list should be non-null iff it contains exceptions.
248 var exceptions = m_faultExceptions;
249 if (exceptions == null) m_faultExceptions = exceptions = new List<ExceptionDispatchInfo>(1);
250 else Contract.Assert(exceptions.Count > 0, "Expected existing exceptions list to have > 0 exceptions.");
252 // Handle Exception by capturing it into an ExceptionDispatchInfo and storing that
253 var exception = exceptionObject as Exception;
254 if (exception != null)
256 exceptions.Add(ExceptionDispatchInfo.Capture(exception));
258 else
260 // Handle ExceptionDispatchInfo by storing it into the list
261 var edi = exceptionObject as ExceptionDispatchInfo;
262 if (edi != null)
264 exceptions.Add(edi);
266 else
268 // Handle enumerables of exceptions by capturing each of the contained exceptions into an EDI and storing it
269 var exColl = exceptionObject as IEnumerable<Exception>;
270 if (exColl != null)
272 #if DEBUG
273 int numExceptions = 0;
274 #endif
275 foreach (var exc in exColl)
277 #if DEBUG
278 Contract.Assert(exc != null, "No exceptions should be null");
279 numExceptions++;
280 #endif
281 exceptions.Add(ExceptionDispatchInfo.Capture(exc));
283 #if DEBUG
284 Contract.Assert(numExceptions > 0, "Collection should contain at least one exception.");
285 #endif
287 else
289 // Handle enumerables of EDIs by storing them directly
290 var ediColl = exceptionObject as IEnumerable<ExceptionDispatchInfo>;
291 if (ediColl != null)
293 exceptions.AddRange(ediColl);
294 #if DEBUG
295 Contract.Assert(exceptions.Count > 0, "There should be at least one dispatch info.");
296 foreach(var tmp in exceptions)
298 Contract.Assert(tmp != null, "No dispatch infos should be null");
300 #endif
302 // Anything else is a programming error
303 else
305 throw new ArgumentException(Environment.GetResourceString("TaskExceptionHolder_UnknownExceptionType"), "exceptionObject");
312 // If all of the exceptions are ThreadAbortExceptions and/or
313 // AppDomainUnloadExceptions, we do not want the finalization
314 // probe to propagate them, so we consider the holder to be
315 // handled. If a subsequent exception comes in of a different
316 // kind, we will reactivate the holder.
317 for (int i = 0; i < exceptions.Count; i++)
319 var t = exceptions[i].SourceException.GetType();
320 if (t != typeof(ThreadAbortException) && t != typeof(AppDomainUnloadedException))
322 MarkAsUnhandled();
323 break;
325 else if (i == exceptions.Count - 1)
327 MarkAsHandled(false);
332 /// <summary>
333 /// A private helper method that ensures the holder is considered
334 /// unhandled, i.e. it is registered for finalization.
335 /// </summary>
336 private void MarkAsUnhandled()
338 // If a thread partially observed this thread's exceptions, we
339 // should revert back to "not handled" so that subsequent exceptions
340 // must also be seen. Otherwise, some could go missing. We also need
341 // to reregister for finalization.
342 if (m_isHandled)
344 GC.ReRegisterForFinalize(this);
345 m_isHandled = false;
349 /// <summary>
350 /// A private helper method that ensures the holder is considered
351 /// handled, i.e. it is not registered for finalization.
352 /// </summary>
353 /// <param name="calledFromFinalizer">Whether this is called from the finalizer thread.</param>
354 internal void MarkAsHandled(bool calledFromFinalizer)
356 if (!m_isHandled)
358 if (!calledFromFinalizer)
360 GC.SuppressFinalize(this);
363 m_isHandled = true;
367 /// <summary>
368 /// Allocates a new aggregate exception and adds the contents of the list to
369 /// it. By calling this method, the holder assumes exceptions to have been
370 /// "observed", such that the finalization check will be subsequently skipped.
371 /// </summary>
372 /// <param name="calledFromFinalizer">Whether this is being called from a finalizer.</param>
373 /// <param name="includeThisException">An extra exception to be included (optionally).</param>
374 /// <returns>The aggregate exception to throw.</returns>
375 internal AggregateException CreateExceptionObject(bool calledFromFinalizer, Exception includeThisException)
377 var exceptions = m_faultExceptions;
378 Contract.Assert(exceptions != null, "Expected an initialized list.");
379 Contract.Assert(exceptions.Count > 0, "Expected at least one exception.");
381 // Mark as handled and aggregate the exceptions.
382 MarkAsHandled(calledFromFinalizer);
384 // If we're only including the previously captured exceptions,
385 // return them immediately in an aggregate.
386 if (includeThisException == null)
387 return new AggregateException(exceptions);
389 // Otherwise, the caller wants a specific exception to be included,
390 // so return an aggregate containing that exception and the rest.
391 Exception[] combinedExceptions = new Exception[exceptions.Count + 1];
392 for (int i = 0; i < combinedExceptions.Length - 1; i++)
394 combinedExceptions[i] = exceptions[i].SourceException;
396 combinedExceptions[combinedExceptions.Length - 1] = includeThisException;
397 return new AggregateException(combinedExceptions);
400 /// <summary>
401 /// Wraps the exception dispatch infos into a new read-only collection. By calling this method,
402 /// the holder assumes exceptions to have been "observed", such that the finalization
403 /// check will be subsequently skipped.
404 /// </summary>
405 internal ReadOnlyCollection<ExceptionDispatchInfo> GetExceptionDispatchInfos()
407 var exceptions = m_faultExceptions;
408 Contract.Assert(exceptions != null, "Expected an initialized list.");
409 Contract.Assert(exceptions.Count > 0, "Expected at least one exception.");
410 MarkAsHandled(false);
411 return new ReadOnlyCollection<ExceptionDispatchInfo>(exceptions);
414 /// <summary>
415 /// Gets the ExceptionDispatchInfo representing the singular exception
416 /// that was the cause of the task's cancellation.
417 /// </summary>
418 /// <returns>
419 /// The ExceptionDispatchInfo for the cancellation exception. May be null.
420 /// </returns>
421 internal ExceptionDispatchInfo GetCancellationExceptionDispatchInfo()
423 var edi = m_cancellationException;
424 Contract.Assert(edi == null || edi.SourceException is OperationCanceledException,
425 "Expected the EDI to be for an OperationCanceledException");
426 return edi;