question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

on async Main, unhandled exception unregisters before capturing crash

See original GitHub issue
static void Main(string[] args)
{
    using var _ = SentrySdk.Init();
    throw null;
}

This exception is captured as expected by the unhandled exception handler.

static async Task Main(string[] args)
{
    using var _ = SentrySdk.Init();
    throw null;
}

The code above doesn’t work as expected. Before calling the UnhandledException handler the SDK goes out of the using “block” and unregisters the unhandled exception integration.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:3
  • Comments:10 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
Tyrrrzcommented, Sep 15, 2020

This doesn’t look like a bug in sentry, but more like an expected behavior due to misuse of async. When you add async modifier to the method, the throw expression changes meaning. Instead of throwing an exception immediately on the stack, they are wrapped in a Task.

You can observe this behavior in the following example:

void A()
{
    throw null;
}

async Task B()
{
    throw null;
}

void C()
{
    // Will throw immediately
    A();

    // Will not throw
    var task = B();

    // Can inspect exception. Awaiting the task will also propagate the exception.
    task.Exception;
}

Here are the two methods from your original example, decompiled:

public static class Program
{
    private static void Main(string[] args)
    {
        IDisposable disposable = SentrySdk.Init();
        try
        {
            throw null;
        }
        finally
        {
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }
    }
}
[CompilerGenerated]
    private sealed class <Main>d__0 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder <>t__builder;

        public string[] args;

        private IDisposable <_>5__1;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                <_>5__1 = SentrySdk.Init();
                try
                {
                    throw null;
                }
                finally
                {
                    if (num < 0 && <_>5__1 != null)
                    {
                        <_>5__1.Dispose();
                    }
                }
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
            }
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    [AsyncStateMachine(typeof(<Main>d__0))]
    [DebuggerStepThrough]
    private static Task Main(string[] args)
    {
        <Main>d__0 stateMachine = new <Main>d__0();
        stateMachine.args = args;
        stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
        stateMachine.<>1__state = -1;
        AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
        <>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

The latter generates a (useless) state machine and the exception actually gets caught and stored in Task.Exception. Because of that, the unhandled exception event is never triggered at all.

Note, the compiler also warns when you write code like this, which kind of implies that it’s probably a mistake.

0reactions
mattjohnsonpintcommented, Apr 20, 2023

This is fully resolved between #2103 and #2319. Async main, and top-level async main, should work fine now (after those are both released).

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - Async method exception crashing application
I run this inside a try-catch block but - with JustMyCode disabled - I get an error originating from somewhere inside the third...
Read more >
Bug - async and uncaught Exceptions
Hello everyone, In one of the projects I'm working on, we're using async and await in order to execute code asynchronously.
Read more >
Sentry/CHANGELOG and Sentry Releases (Page 8) | LibHunt
Update unobserved task exception integration (#2034) ... On async Main, dont unregister unhandled exception before capturing crash (#321) ...
Read more >
Async Await crash on iOS14 with Xcode 13.2.1 - Compiler
I am observing a crash on iOS14 device when I am using the new concurrency model in Xcode 13.2.1 which is backward compatible....
Read more >
Defining Asynchronous JavaScript Code
catch (function(err) { // err will contain an Error object, either due to an unhandled error or to $reject(...) having been called $parameters.Success...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found