runtime: Allow ignoring unhandled exceptions in UnhandledException event handler

Background and Motivation

Scenarios like designers or REPLs that host user provided code are not able to handle unhandled exceptions thrown by the user provided code. Unhandled exceptions on finalizer thread, threadpool threads or user created threads will take down the whole process. This is not desirable experience for these type of scenarios.

The discussion that lead to this proposal is in https://github.com/dotnet/runtime/issues/39587

Proposed API

Allow ignoring unhandled exceptions on threads created by the runtime from managed UnhandledException handler:

 namespace System
 {
     public class UnhandledExceptionEventArgs
     {
         // Existing property. Always true in .NET Core today. The behavior will be changed to:
+        // - `false` for exceptions that can be ignored (ie thread was created by the runtime)
+        // - `true` for exceptions that cannot be ignored (ie foreign thread or other situations when it is not reasonably possible to continue execution)
         // This behavior is close to .NET Framework 1.x behavior of this property.
         public bool IsTerminating { get; }

+        // The default value is false. The event handlers can set it to true to make
+        // runtime ignore the exception. It has effect only when IsTerminating is false.
+        // The documentation will come with usual disclaimer for bad consequences of ignoring exceptions
+        public bool Ignore { get; set; }
     }
 }

Usage Examples

AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
    if (DesignMode && !e.IsTerminating)
    {
        DisplayException(e.ExceptionObject);
        e.Ignore = true;
    }
};

Alternative Designs

Unmanaged hosting API that enables this behavior. (CoreCLR has poorly documented and poorly tested configuration option for this today.)

Similar prior art:

Risks

This API can be abused to ignore unhandled exceptions in scenarios where it is not warranted.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 14
  • Comments: 19 (11 by maintainers)

Most upvoted comments

It seems this issue is also blocking an important plugin for Unreal Engine ( https://github.com/nxrighthere/UnrealCLR/issues/33 ) while this was moved to the Future milestone, I’d like to push this to be taken into consideration for .NET 6 or even .NET 7. It’s kinda discouraging to see it stuck in that milestone that only god-knows-when will be done.

Considering the positive impact that project is making in the UE community as a whole, I think it’s important to look at.

Excluding the UE topic, this issue is pretty valid to me in some apps I’ve developed in the past, too. Haven’t needed it, but it would’ve been a good addition to some workarounds I’ve had to implement.

We need this functionality.

I don’t like Ignorable \ Ignore. Whoever handle the unhandled exception may not necessarily ignore it, maybe they will choose to not just take down the current process, but the whole OS as well. Or they could choose to ignore the unhandled exception. Point is, we only know that someone handled it. We don’t know how they handled it. Ignoring it is just one way of handling it.

Better names: bool CanBeHandled; bool Handled;

Video

  • Looks good as proposed
namespace System
{
    public partial class UnhandledExceptionEventArgs : EventArgs
    {
        // Existing property.
        // public bool IsTerminating { get; }
        
        public bool Ignore { get; set; }
    }
}

Still not a great name.

Proposed API

Allow ignoring unhandled exceptions on threads created by the runtime from new managed UnhandledExceptionThrowing handler:

namespace System
{
    public class AppDomain
    {
        public event UnhandledExceptionThrowingEventHandler? UnhandledExceptionThrowing;
    }

    public delegate void UnhandledExceptionThrowingEventHandler(object sender, UnhandledExceptionThrowingEventArgs e);

    public class UnhandledExceptionThrowingEventArgs : EventArgs
    {
        public object ExceptionObject { get; }

        // - `true` for exceptions that can be ignored (ie thread was created by the runtime)
        // - `false` for exceptions that cannot be ignored (ie foreign thread or other situations when it is not reasonably possible to continue execution)
        public bool Ignorable { get; }

        // The default value is false. The event handlers can set it to true to make
        // runtime ignore the exception. It has effect only when Ignorable is false.
        // The documentation will come with usual disclaimer for bad consequences of ignoring exceptions
        public bool Ignore { get; set; }
    }
}

The exception will be reported in existing UnhandledException event, whether it’s ignored or not. UnhandledExceptionEventArgs.IsTerminating will be false if the exception was ignorable and ignored.

Usage Examples

AppDomain.CurrentDomain.UnhandledExceptionThrowing += (sender, e) =>
{
    if (DesignMode && e.Ignorable)
    {
        DisplayException(e.ExceptionObject);
        e.Ignore = true;
    }
};