【问题标题】:F# MailboxProcessor memory leak in try/catch blockF#MailboxProcessor try/catch 块中的内存泄漏
【发布时间】:2014-06-03 05:43:16
【问题描述】:

更新在 John Palmer 在 cmets 中指出的明显错误之后。

以下代码导致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# 代理中常见的内存泄漏情况,因为代码非常简单和通用。

【问题讨论】:

    标签: memory-leaks f# try-catch tail-recursion agents


    【解决方案1】:

    解决方案:

    结果证明这是一个更大问题的一部分:如果递归调用是在 try/catch 块中进行的,则函数不能是尾递归的,因为它必须能够展开堆栈,如果抛出异常,因此必须保存调用堆栈信息。

    更多详情:

    Tail recursion and exceptions in 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
    )
    

    【讨论】:

      【解决方案2】:

      我怀疑问题实际上在这里:

      while true do
          agent.Post "hello"
      

      您发布的所有"hello"s 都必须存储在内存中的某个位置,并且推送的速度比printf 的输出速度要快得多

      【讨论】:

      • 太棒了。该死,就在我以为我已经找到了最初问题的原因时。看起来需要更多调查。
      • 我已更新问题以更好地反映我的问题并避免该错误。
      【解决方案3】:

      在这里查看我的旧帖子http://vaskir.blogspot.ru/2013/02/recursion-and-trywithfinally-blocks.html

      • 为了满足本站规则的随机字符 *

      【讨论】:

      • 如果您总结博客文章的内容而不是简单地在答案中添加链接,这个答案会更好。仅链接答案的缺点是用处不大(更难扫描答案以找到好的信息)并且容易受到链接失效的影响。
      【解决方案4】:

      基本上,在返回之后执行的任何操作(例如 try/with/finally/dispose)都会阻止尾调用。

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

      还有一些工作正在进行让编译器警告缺少尾递归:https://github.com/fsharp/fslang-design/issues/82

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-22
        • 2012-08-16
        • 1970-01-01
        相关资源
        最近更新 更多