简体   繁体   中英

Restart MailboxProcessor after failure?

I'm trying to get started working with agents in F# via the MailboxProcessor<'Msg> class, and I quickly realized I had no proper handling for exceptions. In a Haskellian world, there wouldn't be any exceptions, and so the proper way to handle issues would be to simply provide them as a response case; so an agent could reply with something like:

type AgentResponse =
    | HandledData of string
    | InvalidData of string

One could then call the agent's .PostAndReply method and get an InvalidData containing a message indicating why the data is invalid. However, this isn't Haskell, and sometimes exceptions happen. So if I do this:

let agent =
    new MailboxProcessor<string * AsyncReplyChannel<AgentResponse>>(fun inbox ->
        async {
            while true do
                let! (msg, replyChannel) = inbox.Receive()
                if msg = "die" then failwith "Unknown exception encountered!"
                else replyChannel.Reply(HandledData(msg))
        })

agent.Start()
agent.PostAndReply (fun rc -> "die", rc)
agent.PostAndReply (fun rc -> "Test", rc)

The second call to agent.PostAndReply blocks indefinitely. When not using AsyncReplyChannel and therefore just calling agent.Post , the calls don't block, but once the agent encounters an exception, new messages just stay in the queue. Restarting the agent in either case appears impossible, as the agent.Start function returns an InvalidOperationException when called again, and the natural way to handle this seems to be to create a new agent with a clean state, but then all queued messages are lost.

Beyond just wrapping the entire body of an agent in try..with , is there any good way to get an agent to continue running after an exception? Alternatively, has a "standard" way of dealing with this been established that someone can point me to?

You do have exceptions in Haskell : try Data.List.head [] in ghci....

Unfortunately, lack of dependent types means, in Haskell or in F#, that we can write type correct code which has no computational meaning.

Practically, wrapping the with a try ... with block is not a bad idea for dealing with exception. You dont need to wrap the entire body, just the non-pure part of your code though.

Then classically, you yield back a value made with the appropriate constructor.

One possible option is a helper type similar to the HandlingMailbox defined by Tomas Petricek over at this question , which runs the body of the agent repeatedly:

type ResilientMailbox<'T> private(f:ResilientMailbox<'T> -> Async<unit>) as self =
    let event = Event<_>()
    let inbox = new MailboxProcessor<_>(fun _inbox ->
        let rec loop() = async {
            try
                return! f self
            with e ->
                event.Trigger(e)
                return! loop()
            }
        loop())
    member __.OnError = event.Publish
    member __.Start() = inbox.Start()
    member __.Receive() = inbox.Receive()
    member __.Post(v:'T) = inbox.Post(v)
    static member Start(f) =
        let mbox = new ResilientMailbox<_>(f)
        mbox.Start()
        mbox

This can be started and run like a normal MailboxProcessor , but will re-run itself if the provided agent body throws an exception.

Edit: Change the inner MailboxProcessor to use a recursive function versus a while true do.. block; the previous code would not stop running when the target function returned normally.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM