簡體   English   中英

Haskell http-conduit web-scraping守護進程因內存不足錯誤而崩潰

[英]Haskell http-conduit web-scraping daemon crashes with out of memory error

我在Haskell編寫了一個守護進程,每隔5分鍾從網頁上抓取一次信息。

該守護進程最初運行正常約50分鍾,但隨后因out of memory (requested 1048576 bytes)意外死亡out of memory (requested 1048576 bytes) 每次我跑它都會在相同的時間后死亡。 將它設置為只睡30秒,它會在8分鍾后死亡。

我意識到刮掉網站的代碼非常低效(在解析9M的html時,從睡眠時的大約30M到250M),所以我重新編寫了它,現在它在解析時只使用了大約15M。 認為這個問題已經解決了,我一夜之間就跑了守護進程,當我醒來的時候實際上使用的內存比當晚少。 我以為我已經完成了,但是在它開始大約20個小時之后,它已經因同樣的錯誤而崩潰了。

我開始研究ghc分析,但我無法讓它工作。 接下來我開始搞亂rts選項 ,我嘗試設置-H64m將默認堆大小設置為大於我的程序使用的大小,並且還使用-Ksize縮小堆棧的最大大小以查看是否會導致崩潰更早。

盡管我做了很多改變,但守護程序在經過多次迭代后似乎仍然會崩潰。 使解析更具內存效率使這個值更高,但它仍然崩潰。 這對我來說沒有意義,因為這些都沒有運行甚至接近使用我的所有內存,更不用說交換空間了。 默認情況下,堆大小應該是無限制的,縮小堆棧大小並沒有什么區別,並且我的所有ulimits都是無限的或遠遠高於守護進程使用的。

在原始代碼中,我將崩潰指向了html解析中的某個地方,但我沒有對更高效的內存版本做同樣的事情,因為運行20個小時需要很長時間。 我不知道這是否有用甚至是有用的,因為它似乎沒有破壞程序的任何特定部分,因為它在崩潰之前成功運行了幾十次迭代。

出於想法,我甚至查看了ghc源代碼中的這個錯誤,它似乎是對mmap的失敗調用,這對我沒有多大幫助,因為我認為這不是問題的根源。

(編輯:代碼重寫並移至帖子末尾)

我是Haskell的新手,所以我希望這是懶惰評估的一些怪癖或其他快速修復的東西。 否則,我是新鮮的想法。

我在FreeBsd 9.1上使用GHC版本7.4.2

編輯:

用靜態html替換下載擺脫了問題,所以我把它縮小到我如何使用http-conduit。 我編輯了上面的代碼以包含我的網絡代碼。 hackage docs提到分享經理所以我已經這樣做了。 它還說,對於http你必須明確地關閉連接,但我認為我不需要為httpLbs這樣做。

這是我的代碼。

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

它的輸出:

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時出錯了。 有任何想法嗎?

此外,當我嘗試編譯並啟用性能分析時,我收到此錯誤:

Could not find module `Network.HTTP.Conduit'
Perhaps you haven't installed the profiling libraries for package `http-conduit-1.8.9'?

實際上,我沒有安裝http-conduit分析庫,我不知道如何。

所以你發現自己是個漏洞。 通過使用編譯器選項和內存設置,您只能推遲程序崩潰的時刻,但無法消除問題的根源,因此無論您在那里設置什么,最終仍會耗盡內存。

我建議您仔細查看所有非純代碼,並使用資源進行初步操作。 檢查是否所有資源都正確釋放。 檢查你是否有一個累積狀態,就像一個不斷增長的無限通道。 當然,正如nm明智地建議的那樣, 對它進行描述

我有一個刮刀,可以在不暫停和下載文件的情況下解析頁面,並且可以同時完成所有操作。 我從未見過使用超過~60M的內存。 我一直用GHC 7.4.2,GHC 7.6.1和GHC 7.6.2進行編譯,兩者都沒有問題。

應該注意的是,問題的根源也可能在您正在使用的庫中。 在我的刮刀中,我使用http-conduithttp-conduit-browserHandsomeSoupHXT

我最終解決了自己的問題。 它似乎是FreeBSD上的一個GHC bug。 我提交了一個錯誤報告並切換到了Linux,現在它在過去的幾天里一直運行良好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM