Утечка памяти в 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