Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use reraise in async workflows in F#?

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)
}
like image 385
Charles Prakash Dasari Avatar asked Aug 23 '11 23:08

Charles Prakash Dasari


2 Answers

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.

like image 85
Stephen Swensen Avatar answered Nov 06 '22 23:11

Stephen Swensen


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

like image 5
John Palmer Avatar answered Nov 06 '22 23:11

John Palmer