1

I have a need in my app to know when an underlying component that is executed dynamically threw a process corrupted state exception so I can log it, and mark the component as bad for loading again and crash my process.

The execution of this component is performed asynchronously and I am using Async.Catch to handle that exception. I tried the following code to test the behavior of Async.Catch and it seems to me that Async.Catch is hung. This is an undesirable effect for me and am suspecting that all PCSE will result in the same behavior.

Anyone know how to get out of this situation?

let a = async {
    System.Threading.Thread.CurrentThread.Abort()
}

let b = async {
    let! m = Async.Catch a
    return match m with
            | Choice1Of2 p -> "hello"
            | Choice2Of2 e -> "Caught: " + e.ToString()
}

Async.RunSynchronously b;;

EDIT 1: I found the documentation that pointing me to use either HandleProcessCorruptedStateExceptionsAttribute together with SecurityCriticalAttribute or use a config entry legacyCorruptedState­­ExceptionsPolicy=true. I don't want to use a config entry if at all possible.

EDIT 2: Based on the suggestion in the comment, I modified the let binding for 'b' as follows:

let b = async {
        try
            let! m = Async.Catch a
            return "hello"
        with
            | :? System.Threading.ThreadAbortException as e -> return "Caught: " + e.ToString()
    }

The program still hangs without returning or throwing.

Charles Prakash Dasari
  • 4,964
  • 1
  • 27
  • 46
  • Not an answer to your question, but I suspect that you could use `try/with` instead of `Async.Catch` to make your async block more readable. – kvb Aug 31 '11 at 20:21
  • Modified the code (see EDIT2), still the same behavior – Charles Prakash Dasari Aug 31 '11 at 20:27
  • 2
    I don't expect it to fix the problem, but I meant for you to do `let! m = a return "hello"` rather than moving `Async.Catch` into the `try` block. – kvb Aug 31 '11 at 20:38
  • 1
    Avoid calling `Thread.Abort`, and live a longer, happier life. http://stackoverflow.com/questions/421389/is-this-thread-abort-normal-and-safe – Brian Aug 31 '11 at 23:39

2 Answers2

5

This is a tricky problem - inside normal function, you can catch the ThreadAbortException and do something in reaction, but you cannot really handle it, because it will be automatically rethrown (and it will eventually kill the thread).

In F# async workflow, the exception is handled and F# async runtime stores it so that it can report it via a continuation, but before it gets a chance to do this, the exception is rethrown by .NET and it kills the thread (thus RunSynchronously hangs).

The problem is - to report exceptions, F# async needs to make some call. The call cannot be made on the current thread (which is being cancelled). If you're expecting the exception, you can start the work in a thread pool and handle it yourself. (F# cannot do that automatically, because it would be too much overhead).

You can use the following helper:

type Microsoft.FSharp.Control.Async with
  static member CatchAbort<'R>(f : unit -> 'R) : Async<'R> =
    async { let hndl = new AutoResetEvent(false)
            let result = ref (Choice3Of3())
            ThreadPool.QueueUserWorkItem(fun _ ->
              try 
                result := Choice1Of3 (f())
                hndl.Set() |> ignore
              with 
                | e -> 
                   // This handler runs correctly even for ThreadAbort
                   result := Choice2Of3 e 
                   hndl.Set() |> ignore) |> ignore
            let! _ = Async.AwaitWaitHandle(hndl) 
            match !result with
            | Choice1Of3(res) -> return res
            | Choice2Of3(exn) -> 
                // Wrap or rethrow the exception in some way
                return raise exn
            | Choice3Of3 _ -> return failwith "unexpected case" }

This starts the specified function (which is not asynchronous) on a thread pool thread. After the function completes or throws, it reports the result back to the original thread, which can resume the workflow.

To adapt your example, this should behave as expected:

let a = async {
    let! value = Async.CatchAbort(fun () ->
      System.Threading.Thread.CurrentThread.Abort()
      "not aborted")
    return value }

let b = async {
    try let! m = a
        printfn "ok"
    with e -> printfn "Caught: %A" e }
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks for a great answer. My main requirement is to handle all the process corrupted state exceptions that occur inside of asynchronous functions. So I will still have the problem of async workflow not reporting the exception occurred in the callback for the reasons you mentioned in your answer?. BTW, I was using thread abort only as an example. Now, it appears that the only recourse I might have to recover from a hung situation is to set a timer and report that my async workflow hung for an unknown reason and then crash the process. Fair? – Charles Prakash Dasari Sep 01 '11 at 00:10
  • I'm not sure how other PCSE exceptions behave, but I assume they terminate the thread in a similiar fashion, so it shouldn't make a difference. You mention that you're calling an external component - I assume that the component is not written in F# and doesn't use async workflows, so you should be able to wrap it inside an async using `CatchAbort` just like in my example. – Tomas Petricek Sep 01 '11 at 00:15
  • Without this additional wrapping, you will not be able to catch the exception, because F# async workflow attempts to catch it internally and then report it (which is not possible if the thread terminates). – Tomas Petricek Sep 01 '11 at 00:16
  • I thank you for the "return raise exn" .. allways had a quarrel with raise inside a async-wf and did a return ... afterwards, never realising that you an do so – Random Dev Sep 01 '11 at 06:08
  • I don't think I understood clearly on how I can wrap calls to my external component with Async.Catch. My external component is asynchronous and follows the Begin/End pattern. I am constructing an async workflow using Async.FromBeginEnd so I can use that seamlessly in my F# code. Could you provide some clarity on what you meant? I don't want to call this API synchronously even though it is in a different thread. – Charles Prakash Dasari Sep 01 '11 at 16:38
3

as you can read here - ThreadAbortException is one of those special exceptions in the CLR. It seems to breaks the Asycn-Pattern pretty hard, so I guess that's the problem here.

Try with another exception and see if that will work (it should).

Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • The documentation states that the finally blocks would run before the thread is terminated and I expect the async workflows to handle this gracefully. Moreover, what is bothering me is that the process would not even crash, but would be hung. I suspect that the callback was never called because of this exception. But wouldn't this be a bug in async workflows? – Charles Prakash Dasari Aug 31 '11 at 21:07
  • 1
    This or (as the docu hints) the first TAE gets converted into something different and the common runtime shuts down the thread in another way without rethrowing it. After all the try/catch(with) in the Async is diffent from the *normal* try/catch as you can read in the docs to computational workflows. But I'm surely no expert but I suspect that this is your problem. I have made similiar experiences in other cases - exceptions like TAE, StackOverflow, etc. are allways a problem and it's normaly better to shutdown the app in those cases. Up to now I allways found a redesign to work without Abort – Random Dev Aug 31 '11 at 21:18