[英]F# MailboxProcessor memory leak in try/catch block
Updated after obvious error pointed out by John Palmer in the comments. 在John Palmer在评论中指出明显错误后进行了更新 。
The following code results in OutOfMemoryException
: 以下代码导致
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
The error is gone if I don't use try/catch block. 如果我不使用try / catch块,则错误消失了。
Increasing the sleep time only postpones the error. 增加睡眠时间只会延迟错误。
Update 2: I guess the issue here is that the function stops being tail recursive as the recursive call is no longer the last one to execute. 更新2:我想这里的问题是该函数不再是尾递归,因为递归调用不再是最后一个要执行的调用。 Would be nice for somebody with more F# experience to desugar it as I'm sure this is a common memory-leak situation in F# agents as the code is very simple and generic.
对于具有更多F#经验的人来说,将其解糖会很好,因为我确定这是F#代理中常见的内存泄漏情况,因为代码非常简单且通用。
Solution: 解:
It turned out to be a part of a bigger problem: the function can't be tail-recursive if the recursive call is made within try/catch block as it has to be able to unroll the stack if the exception is thrown and thus has to save call stack information. 事实证明,这是一个更大问题的一部分: 如果在try / catch块中进行了递归调用,则该函数不能为尾递归,因为如果引发异常,它必须能够展开堆栈保存呼叫堆栈信息。
More details here: 此处有更多详细信息:
Tail recursion and exceptions in F# F#中的尾递归和异常
Properly rewritten code (separate try/catch and return): 正确重写的代码(单独的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
)
I suspect the issue is actually here: 我怀疑问题实际上在这里:
while true do
agent.Post "hello"
All the "hello"
s that you post have to be stored in memory somewhere and will be pushed much faster than the output can happen with printf
您发布的所有
"hello"
都必须存储在某个位置的内存中,并且其推送速度远快于printf
的输出速度
See my old post here http://vaskir.blogspot.ru/2013/02/recursion-and-trywithfinally-blocks.html 在这里查看我的旧帖子http://vaskir.blogspot.ru/2013/02/recursion-and-trywithfinally-blocks.html
Basically anything that is done after the return (like a try/with/finally/dispose) will prevent tail calls. 基本上,返回之后所做的任何事情(例如try / with / finally / dispose)都将阻止尾部调用。
See https://blogs.msdn.microsoft.com/fsharpteam/2011/07/08/tail-calls-in-f/ 请参阅https://blogs.msdn.microsoft.com/fsharpteam/2011/07/08/tail-calls-in-f/
There is also work underway to have the compiler warn about lack of tail recursion: https://github.com/fsharp/fslang-design/issues/82 还有一些工作正在进行,以使编译器警告缺少尾递归: https : //github.com/fsharp/fslang-design/issues/82
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.