[英]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-conduit
, http-conduit-browser
, HandsomeSoup
和HXT
。
我最終解決了自己的問題。 它似乎是FreeBSD上的一個GHC bug。 我提交了一個錯誤報告並切換到了Linux,現在它在過去的幾天里一直運行良好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.