I needed to reraise an exception that occurs while executing an async block, after logging the exception.
When I do the following the compiler thinks that I am not calling the reraise function from within the handler. What am I doing wrong?
let executeAsync context = async {
traceContext.Properties.Add("CorrelationId", context.CorrelationId)
try
do! runAsync context
return None
with
| e when isCriticalException(e) ->
logCriticalException e
reraise()
| e ->
logException e
return Some(e)
}
Rough! I think this is impossible, because reraise corresponds to a special IL instruction that grabs the exception from the top of the stack, but the way async expressions are compiled into a chain of continuations, I don't think the semantics hold!
For the same reason, the following won't compile either:
try
(null:string).ToString()
with e ->
(fun () -> reraise())()
In these situations, where I need to handle the exception outside of the actual with
body, and would like to emulate reraise
(that is, preserve the stack trace of the exception), I use this solution, so all together your code would look like:
let inline reraisePreserveStackTrace (e:Exception) =
let remoteStackTraceString = typeof<exn>.GetField("_remoteStackTraceString", BindingFlags.Instance ||| BindingFlags.NonPublic);
remoteStackTraceString.SetValue(e, e.StackTrace + Environment.NewLine);
raise e
let executeAsync context = async {
traceContext.Properties.Add("CorrelationId", context.CorrelationId)
try
do! runAsync context
return None
with
| e when isCriticalException(e) ->
logCriticalException e
reraisePreserveStackTrace e
| e ->
logException e
return Some(e)
}
Update: .NET 4.5 introduced ExceptionDispatchInfo which may allow a cleaner implementation of reraisePreserveStackTrace
above.
I ran in to a similar problem, in a different context, but it boils down to this.
Exceptions cannot be thrown onto a different thread - calling reraise()
would require an exception handler running in some sense 'above' the original async block in the code.
let runAsync context = async {return ()}
let isCriticalException e = true
let logCriticalException e = ()
let logException e = ()
let executeAsync context =
async {
do! runAsync context
return None
}
let run =
match executeAsync 5 |> Async.Catch |> Async.RunSynchronously with
|Choice1Of2(t) ->
printfn "%A" t
None
|Choice2Of2(exn) ->
match exn with
| e when isCriticalException(e) ->
logCriticalException e
raise (new System.Exception("See inner exception",e)) //stack trace will be lost at this point if the exn is not wrapped
| e ->
logException e
Some(e)
Note, we still can't use reraise, as we are now calling on a different thread, so we wrap the exception inside another one
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With