`return reraise()` under an `async{}` context should be possible
At the moment, return reraise() inside a try...with block that is inside an async{} block is met with a compiler error. More info: https://stackoverflow.com/questions/7168801/how-to-use-reraise-in-async-workflows-in-f
The existing way of approaching this problem in F# is just wrapping the exception with a new one via return raise(Exception("Some new msg", previousEx))
Pros and Cons
The advantages of making this adjustment to F# are:
- No need to needlessly wrap already-thrown exceptions into new ones, so that exception info is preserved without the need to investigate the innerException.
- No need to invent new keywords,
return reraise()could just become valid. - There's even a way to implement this natively already in .NET, thanks to the addition of ExceptionDispatchInfo (https://msdn.microsoft.com/en-us/library/system.runtime.exceptionservices.exceptiondispatchinfo(v=vs.110).aspx) in .NET4.5. F# compiler could just consume this when confronting
return reraise().
The disadvantages of making this adjustment to F# are:
- I believe there are not any disadvantages.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): ?
Affidavit (please submit!)
Please tick this by placing a cross in the box:
- [x] This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
- [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate
- [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
- [x] This is not a breaking change to the F# language design
- [x] I or my company would be willing to help implement and/or test this
I haven't tried this yet with async, but for a true complement of reraise (that is, the same semantics) you may consider this solution from SO.
The solution works in places where there isn't an actual try-with block from a compiler perspective, which is essentially the same here, because the computation expression wraps it into handlers, which are not in scope of the try-with.
type Ex =
/// Modify the exception, preserve the stacktrace and add the current stack,
/// then throw (.NET 2.0+). This puts the origin point of the exception on
/// top of the stacktrace. Can be used in computation expression builders
/// that define a TryWith
static member inline throwPreserve ex =
let preserveStackTrace =
typeof<Exception>.GetMethod("InternalPreserveStackTrace", BindingFlags.Instance ||| BindingFlags.NonPublic)
(ex, null)
|> preserveStackTrace.Invoke // alters the exn, preserves its stacktrace
|> ignore
raise ex
That SO thread has a few other solutions as well (including your mentioned use of ExceptionDispatchInfo). Your current workaround by using raise and wrapping, is that it creates a new exception which may lose information and will change the stacktrace. reraise doesn't, but the semantics of the latter require a catch-block, and I wouldn't be surprised if that's a requirement from ILASM.
That said, if the compiler can "catch" (poor choice of words) that reraise is used from within a TryWith, it could wrap it like above. Or, more generally, just always wrap it like the throwPreserve, which would greatly enhance the usability of reraise.
Of course, either solution requires reraise to take an argument (and since there is no compiler-visible, or even required catch-context, there is no known exception on the stack), so perhaps reraiseWith?
Using reflection to get private elements is already a no-no for me because it might break in Mono, but thanks for the follow-up.
@knocte, agreed. I didn't chime in against this proposal, instead, I like it, but I wanted to show a possible workaround.
Also, I wanted to stress that we should define how a reraise-inside-CE is supposed to behave, since there isn't necessarily a known exception on the stack. The workaround above shows one possible way how it could behave and perhaps there's some way of attaining that behavior without reflection on private methods, and/or making it cross-platform behaving the same.
You can just use ExceptionDispatchInfo (more info here), which supports capturing and re-throwing an exception, without using the rethrow IL opcode, which cannot be used in an async context.
open System
open System.Runtime.ExceptionServices
type Exception with
member this.Reraise () =
(ExceptionDispatchInfo.Capture this).Throw ()
Unchecked.defaultof<_>
Then you can just do something like this:
let task = async {
try return! stuff
with ex ->
do log ex
return ex.Reraise ()
}
@nikonthethird is that any different from what I already mentioned previously? (Note the 2 links I included.)
@knocte, I completely overread that. So yes, this is the same thing.
I'm having this issue with task CE as well. This makes it very hard to do telemetry and other operational things, where I want to catch something to report it (to the exception tracker and to my observability system), but where I can't do the reraise.
@pbiggar thanks for chiming in. As for me, I would be willing to put a bounty on this to be done by some bounty-hunter, would anyone here contribute to the bounty?
@dsyme I see no reason not to allow this, any concerns?
@dsyme given we're already using EDI in other places should we just emit EDI.Throw where reraise is not accepted today?