Created
November 15, 2023 19:55
-
-
Save jwosty/66713eae687cb026d8e2222dd0cc8cf3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System | |
open System.Diagnostics | |
open System.IO | |
open System.Runtime.ExceptionServices | |
module ThirdPartyLibrary = | |
// Represents some low-level async "primitive" which is used in many many places and which may fail. This type of | |
// method/function is not typically what you're interested in when examining stacktraces. | |
// Example: a database query function from a thid-party library | |
let doWorkAsync crash = ExtraTopLevelOperators.async { | |
do! Async.Sleep 100 | |
if crash then | |
raise (IOException("Oh no!")) | |
} | |
// Helper function to improve stacktraces. This is the recommended way to augment (not replace) exception stacktraces | |
// with the caller's stack frame information | |
// See: https://stackoverflow.com/a/17091351/1231925 | |
let inline rethrowAsync (computation: Async<'a>) = async { | |
try | |
return! computation | |
with e -> | |
let s = StackTrace() | |
Debug.WriteLine $"StackTrace: %O{s}" | |
Debug.WriteLine $"Rethrowing: %s{e.StackTrace}" | |
Debug.WriteLine "" | |
ExceptionDispatchInfo.Throw e | |
return Unchecked.defaultof<_> // unreachable, but the compiler doesn't realize that | |
} | |
// Application function. We need this to show up in stack traces | |
let doLoopAsync (n: int) = async { | |
for n in n .. -1 .. 0 do | |
printfn "n = %d" n | |
let crash = n = 6 | |
do! rethrowAsync (ThirdPartyLibrary.doWorkAsync crash) | |
} | |
// Higher level application function. We also need this to show up in stacktraces | |
let mainAsync () = async { | |
let n = 10 | |
printfn "Starting with iterations: %d" n | |
do! rethrowAsync (doLoopAsync n) | |
printfn "Done." | |
} | |
[<EntryPoint>] | |
let main _ = | |
Async.RunSynchronously (mainAsync ()) | |
// Even when using the suggested workarounds to recapture stack trace information by rethrowing, the stacktrace we | |
// get is completely useless: | |
// | |
// Unhandled exception. System.IO.IOException: Oh no! | |
// at Program.ThirdPartyLibrary.doWorkAsync@12-1.Invoke(Unit _arg1) in C:\Users\jwostenberg\Documents\Code\FSharpSandbox\ConsoleApp2\Program.fs:line 13 | |
// at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 525 | |
// at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 112 | |
// --- End of stack trace from previous location --- | |
// at Microsoft.FSharp.Control.AsyncResult`1.Commit() in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 453 | |
// at Microsoft.FSharp.Control.AsyncPrimitives.QueueAsyncAndWaitForResultSynchronously[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 1133 | |
// at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronously[T](CancellationToken cancellationToken, FSharpAsync`1 computation, FSharpOption`1 timeout) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 1160 | |
// at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 1504 | |
// at Program.main(String[] _arg1) in C:\Users\jwostenberg\Documents\Code\FSharpSandbox\ConsoleApp2\Program.fs:line 49 | |
// | |
// Question of the day: what blew up? ¯\_(ツ)_/¯ Everything useful (doLoopAsync and mainAsync) still got lost, so | |
// there's no way to know. It's especially confounding since the rethrow helper is *supposed to work*, and indeed | |
// the debug print line shows that it appears to! I'm even fairly suspicious that this used to work at some point. | |
// | |
// | |
// Bonus points -- take this snippet: | |
// Async.RunSynchronously (async { | |
// try return! mainAsync () | |
// with e -> eprintfn "%O" e | |
// }) | |
// Somehow, this actually does show the complete stacktrace: | |
// | |
// System.IO.IOException: Oh no! | |
// at Program.ThirdPartyLibrary.doWorkAsync@12-1.Invoke(Unit _arg1) in C:\Users\jwostenberg\Documents\Code\FSharpSandbox\ConsoleApp2\Program.fs:line 13 | |
// at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, b result1, FSharpFunc`2 userCode) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 525 | |
// at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 112 | |
// --- End of stack trace from previous location --- | |
// at Program.doLoopAsync@36-4.Invoke(Exception _arg1) | |
// at Program.doLoopAsync@36-7.Invoke(Exception exn) | |
// at Microsoft.FSharp.Control.AsyncPrimitives.CallFilterThenInvoke[T](AsyncActivation`1 ctxt, FSharpFunc`2 filterFunction, ExceptionDispatchInfo edi) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 543 | |
// at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 112 | |
// --- End of stack trace from previous location --- | |
// at Program.mainAsync@43-3.Invoke(Exception _arg1) | |
// at Program.mainAsync@43-6.Invoke(Exception exn) | |
// at Microsoft.FSharp.Control.AsyncPrimitives.CallFilterThenInvoke[T](AsyncActivation`1 ctxt, FSharpFunc`2 filterFunction, ExceptionDispatchInfo edi) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 543 | |
// at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 112 | |
0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment