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.