Утечка памяти в F# MailboxProcessor в блоке try/catch

Обновлено после явной ошибки, указанной Джоном Палмером в комментариях.

Следующий код приводит к OutOfMemoryException:

let agent = MailboxProcessor<string>.Start(fun agent ->

    let maxLength = 1000

    let rec loop (state: string list) i = async {
        let! msg = agent.Receive()

        try        
            printfn "received message: %s, iteration: %i, length: %i" msg i state.Length
            let newState = state |> Seq.truncate maxLength |> Seq.toList
            return! loop (msg::newState) (i+1)
        with
        | ex -> 
            printfn "%A" ex
            return! loop state (i+1)
    }

    loop [] 0
)

let greeting = "hello"

while true do
    agent.Post greeting
    System.Threading.Thread.Sleep(1) // avoid piling up greetings before they are output

Ошибка исчезнет, ​​если я не использую блок try/catch.

Увеличение времени ожидания только откладывает ошибку.

Обновление 2: я предполагаю, что проблема здесь в том, что функция перестает быть хвостовой рекурсивной, поскольку рекурсивный вызов больше не выполняется последним. Было бы хорошо, если бы кто-то с большим опытом работы с F# удалил его, поскольку я уверен, что это распространенная ситуация утечки памяти в агентах F#, поскольку код очень прост и универсален.

4 ответа

Решение

Решение:

Это оказалось частью большой проблемы: функция не может быть хвостовой рекурсивной, если рекурсивный вызов выполняется внутри блока try/catch, поскольку она должна иметь возможность развернуть стек, если выброшено исключение, и поэтому имеет сохранить информацию стека вызовов.

Подробнее здесь:

Хвостовая рекурсия и исключения в F#

Правильно переписанный код (отдельный try/catch и return):

let agent = MailboxProcessor<string>.Start(fun agent ->

    let maxLength = 1000

    let rec loop (state: string list) i = async {
        let! msg = agent.Receive()

        let newState = 
            try        
                printfn "received message: %s, iteration: %i, length: %i" msg i state.Length
                let truncatedState = state |> Seq.truncate maxLength |> Seq.toList
                msg::truncatedState
            with
            | ex -> 
                printfn "%A" ex
                state

        return! loop newState (i+1)
    }

    loop [] 0
)

Я подозреваю, что проблема на самом деле здесь:

while true do
    agent.Post "hello"

Все "hello"То, что вы публикуете, должно храниться где-то в памяти и будет выдвигаться гораздо быстрее, чем вывод printf

Смотрите мой старый пост здесь http://vaskir.blogspot.ru/2013/02/recursion-and-trywithfinally-blocks.html

  • случайные символы, чтобы соответствовать правилам сайта *

В основном все, что делается после возврата (например, try/with/finally/dispose), предотвращает вызовы tail.

См. https://blogs.msdn.microsoft.com/fsharpteam/2011/07/08/tail-calls-in-f/

Также ведется работа по предупреждению компилятора об отсутствии хвостовой рекурсии: https://github.com/fsharp/fslang-design/issues/82

Другие вопросы по тегам