dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.83k stars 773 forks source link

Debugger should not break on f# (F sharp) exception in async blocks #5539

Open vsfeedback opened 5 years ago

vsfeedback commented 5 years ago

The following code causes the debugger to break when the exception is thrown. Clearly the exception is handled and so the debugger should not break.

If I continue execution the program continues to run normally; printing "oops" on the screen as expected.

[<EntryPoint>]
let main argv =
    async {
        try
            do! Async.Sleep(1)
            failwith "oops" // The debugger breaks here but it should not as the exception is handled. It should behave like the C# version
        with
        | ex -> printfn "%s" (ex.Message)
    }
    |> Async.RunSynchronously

    Console.ReadLine() |> ignore
    0 

Compare this with the equivalent C# code. This does not break when the exception is thrown. The F# version should behave the same way and not break!

using System;
using System.Threading.Tasks;

namespace CSharpTaskException
{
    class Program
    {
        static void Main(string[] args)
        {
            async Task DoSomething()
            {
                try
                {
                    await Task.Delay(1);
                    throw new Exception(&quot;oops&quot;); // ****** This does not cause the debugger to break as the exception is handled
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

            DoSomething().Wait();

            Console.ReadLine();
        }
    }
}

This issue has been moved from https://developercommunity.visualstudio.com/content/problem/314461/debugger-should-not-break-on-f-f-sharp-exception-i.html VSTS ticketId: 666263 These are the original issue comments: (no comments) These are the original issue solutions: (no solutions)

cartermp commented 5 years ago

I think this can be safely attributed to the host of different debugging issues with CE, especially async. But it's good to have it tracked.

dsyme commented 5 years ago

Notes

   at Program.main@8-2.Invoke(Unit _arg1)
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, FSharpFunc`2 userCode, b result1)
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction)
   at Microsoft.FSharp.Control.TrampolineHolder.ExecuteWithTrampoline(FSharpFunc`2 firstAction)
   at <StartupCode$FSharp-Core>.$Async.Sleep@1355-1.Invoke(Object _arg3)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.TimerQueueTimer.CallCallback()
   at System.Threading.TimerQueueTimer.Fire()
   at System.Threading.TimerQueue.FireNextTimers()
dsyme commented 5 years ago

The other option is to inline move async/control code to become user code, specifically inlining

from async.fs. This would need an FSharp.Core update and a new entry point for unprotected, non-hijacked invocation of an async (effectively making the "Invoke" function on an async public via a backdoor). In the big picture that would be ok I think.