首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Haskell http-conduit web-scraping守护程序崩溃,内存不足错误

Haskell http-conduit web-scraping守护程序崩溃,内存不足错误
EN

Stack Overflow用户
提问于 2013-02-25 10:09:25
回答 2查看 564关注 0票数 8

我用Haskell编写了一个守护进程,它每5分钟从网页上抓取一次信息。

这个守护进程最初正常运行了大约50分钟,但后来它意外地死于out of memory (requested 1048576 bytes)。每次我运行它时,它都会在相同的时间后死掉。设置它只休眠30秒,它在8分钟后就死了。

我意识到抓取网站的代码内存效率低得令人难以置信(从睡眠时的30M到解析9M的html时的250M ),所以我重写了它,现在它在解析时只额外使用了大约15M。以为问题已经解决了,我连夜运行了守护进程,当我醒来时,它实际上使用了比那天晚上更少的内存。我以为我已经完成了,但是在它启动大约20个小时后,它崩溃了,并出现了同样的错误。

我开始研究ghc分析,但我不能让它工作。接下来,我开始处理rts options,并尝试将-H64m设置为大于我的程序使用的默认堆大小,还使用-Ksize缩小堆栈的最大大小,看看这是否会使它更快崩溃。

尽管我做了每一次更改,但在恒定的迭代次数之后,守护进程似乎仍然会崩溃。使解析的内存效率更高会使这个值更高,但它仍然会崩溃。这对我来说是没有意义的,因为这些都没有运行甚至接近使用我所有的内存,更不用说交换空间了。默认情况下,堆大小应该是无限制的,缩小堆栈大小并没有什么区别,并且我的所有ulimit要么是无限制的,要么明显高于守护进程正在使用的大小。

在原始代码中,我将崩溃定位到html解析中的某个地方,但对于内存效率更高的版本,我没有做同样的事情,因为20小时的运行时间太长了。我甚至不知道这是否有用,因为它似乎并没有破坏程序的任何特定部分,因为它在崩溃之前成功运行了几十次迭代。

出乎意料的是,我甚至在ghc source code中查找了这个错误,它似乎是对mmap的失败调用,这对我没有太大帮助,因为我认为这不是问题的根源。

(编辑:代码重写并移动到post末尾)

我是Haskell的新手,所以我希望这是一些惰性评估的怪癖,或者其他有快速修复的东西。不然的话,我就没点子了。

我在FreeBsd 9.1上使用GHC7.4.2版

编辑:

用静态html替换下载就解决了这个问题,所以我把范围缩小到我是如何使用http-conduit的。我已经编辑了上面的代码以包含我的网络代码。hackage文档提到要共享一个管理器,所以我就这么做了。它还说,对于http,您必须显式地关闭连接,但我不认为我需要为httpLbs这样做。

这是我的代码。

代码语言:javascript
运行
复制
import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import qualified Data.ByteString.Lazy as BL
import Text.Regex.PCRE
import Network.HTTP.Conduit

main :: IO ()
main = do
    manager <- newManager def
    daemonLoop manager

daemonLoop :: Manager -> IO ()
daemonLoop manager = do
    rows <- scrapeWebpage manager
    putStrLn $ "number of rows parsed: " ++ (show $ length rows)
    doSleep
    daemonLoop manager

scrapeWebpage :: Manager -> IO [[BL.ByteString]]
scrapeWebpage manager = do
    putStrLn "before makeRequest"
    html <- makeRequest manager
    -- Force evaluation of html.
    putStrLn $ "html length: " ++ (show $ BL.length html)
    putStrLn "after makeRequest"
    -- Breaks ~10M html table into 2d list of bytestrings.
    -- Max memory usage is about 45M, which is about 15M more than when sleeping.
    return $ map tail $ html =~ pattern
    where
        pattern :: BL.ByteString
        pattern = BL.concat $ replicate 12 "<td[^>]*>([^<]+)</td>\\s*"

makeRequest :: Manager -> IO BL.ByteString
makeRequest manager = runResourceT $ do
    defReq <- parseUrl url
    let request = urlEncodedBody params $ defReq
                    -- Don't throw errors for bad statuses.
                    { checkStatus = \_ _ -> Nothing
                    -- 1 minute.
                    , responseTimeout = Just 60000000
                    }
    response <- httpLbs request manager
    return $ responseBody response

它的输出是:

代码语言:javascript
运行
复制
before makeRequest
html length: 1555212
after makeRequest
number of rows parsed: 3608
...
before makeRequest
html length: 1555212
after makeRequest
bannerstalkerd: out of memory (requested 2097152 bytes)

摆脱正则表达式计算解决了这个问题,但似乎错误发生在联网之后和正则表达式期间,大概是因为我在使用http-conduit时做错了什么。有什么想法吗?

此外,当我尝试在启用性能分析的情况下进行编译时,我得到以下错误:

代码语言:javascript
运行
复制
Could not find module `Network.HTTP.Conduit'
Perhaps you haven't installed the profiling libraries for package `http-conduit-1.8.9'?

实际上,我还没有为http-conduit安装性能分析库,我也不知道如何安装。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-03-03 11:23:41

我最终解决了我自己的问题。这似乎是FreeBSD上的一个GHC bug。我提交了一份bug报告,并切换到Linux,现在它在过去的几天里一直运行得很完美。

票数 3
EN

Stack Overflow用户

发布于 2013-02-25 15:44:38

所以你找到了一个泄密者。通过使用编译器选项和内存设置,您只能推迟程序崩溃的时刻,但不能消除问题的根源,因此无论您在那里设置什么,最终都会耗尽内存。

我建议您仔细浏览所有非纯代码,主要是使用资源的部分。检查所有资源是否正确释放。检查你是否有一个累积的状态,就像一个不断增长的无界通道。当然,正如n.m.,profile it明智地建议的那样。

我有一个抓取器,它可以在不暂停的情况下解析页面和下载文件,并且它可以并发地完成所有这些工作。我从未见过它使用超过60M的内存。我一直在用GHC 7.4.2,GHC 7.6.1和GHC 7.6.2编译它,两者都没有问题。

应该注意的是,问题的根源可能还在于您正在使用的库。在我的抓取器中,我使用了http-conduithttp-conduit-browserHandsomeSoupHXT

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15059075

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档