Перезапустить MailboxProcessor после сбоя?

Я пытаюсь начать работать с агентами на F# через класс MailboxProcessor<'Msg> и быстро понял, что у меня нет надлежащей обработки исключений. В хаскелевском мире не было бы никаких исключений, поэтому правильным способом решения проблем было бы просто предоставить их в качестве случая ответа; чтобы агент мог ответить что-то вроде:

type AgentResponse =
    | HandledData of string
    | InvalidData of string

Затем можно вызвать метод .PostAndReply агента и получить InvalidData, содержащее сообщение, указывающее, почему данные недействительны. Однако это не Haskell, и иногда случаются исключения. Итак, если я сделаю это:

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)

Второй вызов agent.PostAndReply блокируется на неопределенный срок. Когда не используется AsyncReplyChannel и, следовательно, просто вызывается agent.Post, вызовы не блокируются, но как только агент сталкивается с исключением, новые сообщения просто остаются в очереди. Перезапуск агента в любом случае кажется невозможным, так как функция agent.Start возвращает InvalidOperationException при повторном вызове, и кажется, что естественный способ справиться с этим — создать новый агент с чистым состоянием, но тогда все сообщения в очереди будут потеряны.

Помимо того, что все тело агента обернуто в try..with, есть ли хороший способ заставить агент продолжать работать после исключения? В качестве альтернативы, был ли установлен «стандартный» способ справиться с этим, на который кто-то может мне указать?


person Amazingant    schedule 02.01.2015    source источник
comment
Я думаю, что вся идея MailboxProcessor (агента) заключается в том, что он умирает при неудаче. Может быть, вы могли бы использовать другого агента в качестве очереди, а затем использовать TryPostAndReply, где агент очереди запускает нового агента при возникновении ошибки? Если вам нужна высокая производительность, я думаю, что try...catch, вероятно, лучший вариант.   -  person N_A    schedule 02.01.2015
comment
@mydogisbox Просто ловить все кажется менее чем оптимальной идеей, но я согласен, что это также кажется лучшим способом. Проблема, которую я хочу избежать, заключается в том, чтобы хранить две копии сообщений в очереди; например, если бы я написал демон почтовой программы на F#. Демон не должен терять электронные письма из очереди только потому, что агент столкнулся с сетевым исключением, которое я не подумал перехватить, поэтому ему нужно будет хранить сообщения в другом месте, а также в очереди. Такое ощущение, что это противоречит цели агента, имеющего очередь, если мне нужна вторая копия всего в указанной очереди.   -  person Amazingant    schedule 02.01.2015
comment
Я согласен. Интересно, как модель агента erlang справляется с этой проблемой.   -  person N_A    schedule 02.01.2015


Ответы (2)


У вас есть исключения в Haskell: попробуйте Data.List.head [] в ghci....

К сожалению, отсутствие зависимых типов означает, что в Haskell или F# мы можем писать корректный код, который не имеет никакого вычислительного смысла.

На практике обертывание блоком try ... with — неплохая идея для обработки исключений. Вам не нужно обертывать все тело, только нечистую часть вашего кода.

Затем классически вы возвращаете значение, созданное с помощью соответствующего конструктора.

person nicolas    schedule 02.01.2015
comment
просто нечистая часть вашего кода в .NET, разве это не все? :) Ожидание и желание получить чистое ключевое слово: fslang.uservoice.com/forums/245727-f-language/suggestions/ - person Amazingant; 02.01.2015
comment
Возможно, но при этом непринудительный чистый код; тогда зачем вообще нужны такие языки, как Haskell? Кроме того, довольно сложно определить, какие объекты в BCL (и сторонних библиотеках) безопасно использовать в чистом виде. - person Amazingant; 02.01.2015
comment
@nicolas: довольно несправедливое сравнение, поскольку в C нет исключений (игнорирующих сигналы с плавающей запятой), тогда как 90% методов BCL могут генерировать ошибки. - person ildjarn; 02.01.2015

Одним из возможных вариантов является вспомогательный тип, аналогичный HandlingMailbox, определенному Томасом Петричеком в этом вопросе, который запускается тело агента неоднократно:

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

Это может быть запущено и запущено как обычный MailboxProcessor, но будет перезапущено, если предоставленное тело агента выдаст исключение.

Редактировать: изменить внутренний MailboxProcessor, чтобы использовать рекурсивную функцию вместо блока while true do..; предыдущий код не прекращал работу, когда целевая функция возвращалась нормально.

person Amazingant    schedule 02.01.2015