Haskell key input memory leak

时间:2017-04-02 00:43:19

标签: haskell memory-leaks lazy-evaluation

I came up with the following code for the raw-input problem, as discussed in SO Haskell read raw keyboard input之间的差异。不幸的是,当我去ghci时,运行getAllInput,并点击右箭头键,它永远不会返回。除非我很快就杀了它,它似乎占用了我所有的内存,以便其他应用程序停止响应,我必须重新启动操作系统。在活动监视器中,我可以看到ghc进程的内存快速进入千兆字节。

(1)我认为问题出在go的递归调用中,通过在hReady之前调用getChar来懒惰地评估;这意味着hReady保持返回true并且堆栈永远增长。这看起来有道理吗?

(2)我已经习惯了很快会导致堆栈溢出异常的语言,所以它不会阻止我工作。有没有一般的方法来防止这种大规模的内存泄漏?也许以内存使用的硬限制开始ghci?

import System.IO

-- For example, should get "\ESC[C" from the user hitting the right arrow key.
getAllInput :: IO [Char]
getAllInput =
  let
    go :: IO [Char] -> IO [Char]
    go chars = do
      more <- hReady stdin
      if more then go (added chars getChar) else chars
    added :: IO [Char] -> IO Char -> IO [Char]
    added chars char = do
      chars1 <- chars
      char1 <- char
      return (chars1 ++ [char1])
  in do
    hSetBuffering stdin NoBuffering
    firstChar <- getChar
    go (return [firstChar])

我在OS X 10.11.6中运行ghci 7.10.3。我以一些显而易见的方式清理了代码,基本上遵循the similar SO answer:将getChar调用放在自己的行中可以解决问题。但是如果它再次咬我,我想更好地理解这一点。

2 个答案:

答案 0 :(得分:5)

go中有一个无限循环。如果hReady返回true,则再次调用go,然后立即再次调用hReady,这当然会返回true,依此类推。您可能认为added会因go (added chars getChar)而运行,但它不会;它只是构建一个IO动作并将其作为参数传递给go,但该参数仅在hReady返回False时使用。名称chars具有误导性 - chars实际上是一个I / O过程,它将在最终运行时返回一个字符列表。

一般来说,当使用monad时,&#34;正常&#34;函数签名如下:

:: Foo -> Bar -> Baz -> IO Quuz

也就是说,monad(IO)只出现在返回值上,而不是参数上。签名如

:: IO Foo -> IO Bar

通常表示某些高阶正在发生,例如,此函数可能会多次执行其参数或在新的上下文中执行。

我推荐签名

go :: [Char] -> IO [Char]
added :: [Char] -> Char -> IO [Char]

并尝试从那里编译程序。

您还应该尝试将added更改为纯函数

added :: [Char] -> Char -> [Char]

因为它实际上没有任何副作用。但是,实现和使用需要稍微改变一下。

答案 1 :(得分:1)

总结讨论......

(1)正如luqui's answer所解释的,内存泄漏是由无限循环引起的,其中惰性求值阻止调用getChar。添加c <- getChar之类的行会强制进行调用,以便现在可以安全地调用go (added chars c)

(2)启动具有有限堆大小的GHCi,如ghci getchars.hs +RTS -M100m,将在占用所有内存之前中断内存泄漏。有关详细信息,请参阅GHC Users Guide