简体   繁体   English

try / catch块中的F#MailboxProcessor内存泄漏

[英]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

  • random chars in order to satisfy this site rules * 随机字符,以满足本网站规则*

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM